Java服务器动态打包APK安装包(添加渠道等相关信息)

前言

  项目中的手机客户端需要根据下载用户的不同附带不同的信息,所以就需要在WEB服务器端动态生成APK,于是有了下面的经历。

历程

  1.一开始的想法就是在APK本身就是个ZIP压缩包嘛,我把他解压缩,在里面中放一个配置文件,然后下载的时候,WEB服务器根据用户信息的不同动态替换这个配置文件,最后重新压缩成apk,然后APP运行的时候,读取这个配置文件就可以了。代码写好了,生成的安装包放手机一安装,安装包解析失败!

  2.于是各种找资料,好多博客里面也都写Java动态往APK中添加配置文件,修改配置文件,可年代已经久远,加上本人手机安卓9.0了,估计是不行。于是找到一个往ZIP的comment区添加信息的博客,是一种新思路。于是复制代码实现了这个功能,往comments区添加信息,读取信息都可以,但安装的时候还是失败了。博主写的签名时只勾选V1的操作也试了,还是不行,还是不行。原文链接:https://www.jianshu.com/p/013f953d4508

  3.感觉高版本对安装包的解析太严格了,于是又找资料,幸运的发现了美团的github项目:https://github.com/Meituan-Dianping/walle,美团的多渠道打包工具,提供了一个walle-cli-all.jar包,在这个项目下找不到,在另一个项目中找到了这包,先没写代码,用这个包直接运行试了下,添加了一些渠道信息和额外信息,可以运行,放到手机上,安装成功了,有点激动,终于找到个可用的办法了!

代码

  代码不是我原创的,是美团的github项目上下下来的,下面粘贴处我用到的核心代码。

往APK中写入信息:

  1 import java.io.File;
  2 import java.io.FileInputStream;
  3 import java.io.FileOutputStream;
  4 import java.io.IOException;
  5 import java.io.RandomAccessFile;
  6 import java.nio.ByteBuffer;
  7 import java.nio.ByteOrder;
  8 import java.nio.channels.FileChannel;
  9 import java.util.HashMap;
 10 import java.util.Map;
 11 import java.util.Set;
 12 import java.util.UUID;
 13 
 14 
 15 public final class PayloadWriter {
 16     private PayloadWriter() {
 17         super();
 18     }
 19 
 20     /**
 21      * put (id, String) into apk, update if id exists
 22      * @param apkFile apk file
 23      * @param id id
 24      * @param string string content
 25      * @throws IOException
 26      * @throws SignatureNotFoundException
 27      */
 28     public static void put(final File apkFile, final int id, final String string) throws IOException, SignatureNotFoundException {
 29         put(apkFile, id, string, false);
 30     }
 31     /**
 32      * put (id, String) into apk, update if id exists
 33      * @param apkFile apk file
 34      * @param id id
 35      * @param string string
 36      * @param lowMemory if need low memory operation, maybe a little slower
 37      * @throws IOException
 38      * @throws SignatureNotFoundException
 39      */
 40     public static void put(final File apkFile, final int id, final String string, final boolean lowMemory) throws IOException, SignatureNotFoundException {
 41         final byte[] bytes = string.getBytes(ApkUtil.DEFAULT_CHARSET);
 42         final ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
 43         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
 44         byteBuffer.put(bytes, 0, bytes.length);
 45         byteBuffer.flip();
 46         put(apkFile, id, byteBuffer, lowMemory);
 47     }
 48     /**
 49      * put (id, buffer) into apk, update if id exists
 50      *
 51      * @param apkFile apk file
 52      * @param id      id
 53      * @param buffer  buffer
 54      * @throws IOException
 55      * @throws SignatureNotFoundException
 56      */
 57     public static void put(final File apkFile, final int id, final ByteBuffer buffer) throws IOException, SignatureNotFoundException {
 58         put(apkFile, id, buffer, false);
 59     }
 60 
 61     /**
 62      * put (id, buffer) into apk, update if id exists
 63      * @param apkFile apk file
 64      * @param id id
 65      * @param buffer buffer
 66      * @param lowMemory if need low memory operation, maybe a little slower
 67      * @throws IOException
 68      * @throws SignatureNotFoundException
 69      */
 70     public static void put(final File apkFile, final int id, final ByteBuffer buffer, final boolean lowMemory) throws IOException, SignatureNotFoundException {
 71         final Map<Integer, ByteBuffer> idValues = new HashMap<Integer, ByteBuffer>();
 72         idValues.put(id, buffer);
 73         putAll(apkFile, idValues, lowMemory);
 74     }
 75     /**
 76      * put new idValues into apk, update if id exists
 77      *
 78      * @param apkFile  apk file
 79      * @param idValues id value. NOTE: use unknown IDs. DO NOT use ID that have already been used.  See <a href='https://source.android.com/security/apksigning/v2.html'>APK Signature Scheme v2</a>
 80      * @throws IOException
 81      * @throws SignatureNotFoundException
 82      */
 83     public static void putAll(final File apkFile, final Map<Integer, ByteBuffer> idValues) throws IOException, SignatureNotFoundException {
 84         putAll(apkFile, idValues, false);
 85     }
 86     /**
 87      * put new idValues into apk, update if id exists
 88      *
 89      * @param apkFile  apk file
 90      * @param idValues id value. NOTE: use unknown IDs. DO NOT use ID that have already been used.  See <a href='https://source.android.com/security/apksigning/v2.html'>APK Signature Scheme v2</a>
 91      * @param lowMemory if need low memory operation, maybe a little slower
 92      * @throws IOException
 93      * @throws SignatureNotFoundException
 94      */
 95     public static void putAll(final File apkFile, final Map<Integer, ByteBuffer> idValues, final boolean lowMemory) throws IOException, SignatureNotFoundException {
 96         handleApkSigningBlock(apkFile, new ApkSigningBlockHandler() {
 97             @Override
 98             public ApkSigningBlock handle(final Map<Integer, ByteBuffer> originIdValues) {
 99                 if (idValues != null && !idValues.isEmpty()) {
100                     originIdValues.putAll(idValues);
101                 }
102                 final ApkSigningBlock apkSigningBlock = new ApkSigningBlock();
103                 final Set<Map.Entry<Integer, ByteBuffer>> entrySet = originIdValues.entrySet();
104                 for (Map.Entry<Integer, ByteBuffer> entry : entrySet) {
105                     final ApkSigningPayload payload = new ApkSigningPayload(entry.getKey(), entry.getValue());
106                     apkSigningBlock.addPayload(payload);
107                 }
108                 return apkSigningBlock;
109             }
110         }, lowMemory);
111     }
112     /**
113      * remove content by id
114      *
115      * @param apkFile apk file
116      * @param id id
117      * @throws IOException
118      * @throws SignatureNotFoundException
119      */
120     public static void remove(final File apkFile, final int id) throws IOException, SignatureNotFoundException {
121         remove(apkFile, id, false);
122     }
123     /**
124      * remove content by id
125      *
126      * @param apkFile apk file
127      * @param id id
128      * @param lowMemory  if need low memory operation, maybe a little slower
129      * @throws IOException
130      * @throws SignatureNotFoundException
131      */
132     public static void remove(final File apkFile, final int id, final boolean lowMemory) throws IOException, SignatureNotFoundException {
133         PayloadWriter.handleApkSigningBlock(apkFile, new PayloadWriter.ApkSigningBlockHandler() {
134             @Override
135             public ApkSigningBlock handle(final Map<Integer, ByteBuffer> originIdValues) {
136                 final ApkSigningBlock apkSigningBlock = new ApkSigningBlock();
137                 final Set<Map.Entry<Integer, ByteBuffer>> entrySet = originIdValues.entrySet();
138                 for (Map.Entry<Integer, ByteBuffer> entry : entrySet) {
139                     if (entry.getKey() != id) {
140                         final ApkSigningPayload payload = new ApkSigningPayload(entry.getKey(), entry.getValue());
141                         apkSigningBlock.addPayload(payload);
142                     }
143                 }
144                 return apkSigningBlock;
145             }
146         }, lowMemory);
147     }
148 
149     interface ApkSigningBlockHandler {
150         ApkSigningBlock handle(Map<Integer, ByteBuffer> originIdValues);
151     }
152 
153     static void handleApkSigningBlock(final File apkFile, final ApkSigningBlockHandler handler, final boolean lowMemory) throws IOException, SignatureNotFoundException {
154         RandomAccessFile fIn = null;
155         FileChannel fileChannel = null;
156         try {
157             fIn = new RandomAccessFile(apkFile, "rw");
158             fileChannel = fIn.getChannel();
159             final long commentLength = ApkUtil.getCommentLength(fileChannel);
160             final long centralDirStartOffset = ApkUtil.findCentralDirStartOffset(fileChannel, commentLength);
161             // Find the APK Signing Block. The block immediately precedes the Central Directory.
162             final Pair<ByteBuffer, Long> apkSigningBlockAndOffset = ApkUtil.findApkSigningBlock(fileChannel, centralDirStartOffset);
163             final ByteBuffer apkSigningBlock2 = apkSigningBlockAndOffset.getFirst();
164             final long apkSigningBlockOffset = apkSigningBlockAndOffset.getSecond();
165 
166             final Map<Integer, ByteBuffer> originIdValues = ApkUtil.findIdValues(apkSigningBlock2);
167             // Find the APK Signature Scheme v2 Block inside the APK Signing Block.
168             final ByteBuffer apkSignatureSchemeV2Block = originIdValues.get(ApkUtil.APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
169 
170             if (apkSignatureSchemeV2Block == null) {
171                 throw new IOException(
172                         "No APK Signature Scheme v2 block in APK Signing Block");
173             }
174 
175             final boolean needPadding = originIdValues.remove(ApkUtil.VERITY_PADDING_BLOCK_ID) != null;
176             final ApkSigningBlock apkSigningBlock = handler.handle(originIdValues);
177             // replace VERITY_PADDING_BLOCK with new one
178             if (needPadding) {
179                 // uint64:  size (excluding this field)
180                 // repeated ID-value pairs:
181                 //     uint64:           size (excluding this field)
182                 //     uint32:           ID
183                 //     (size - 4) bytes: value
184                 // (extra dummy ID-value for padding to make block size a multiple of 4096 bytes)
185                 // uint64:  size (same as the one above)
186                 // uint128: magic
187 
188                 int blocksSize = 0;
189                 for (ApkSigningPayload payload : apkSigningBlock.getPayloads()) {
190                     blocksSize += payload.getTotalSize();
191                 }
192 
193                 int resultSize = 8 + blocksSize + 8 + 16; // size(uint64) + pairs size + size(uint64) + magic(uint128)
194                 if (resultSize % ApkUtil.ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0) {
195                     int padding = ApkUtil.ANDROID_COMMON_PAGE_ALIGNMENT_BYTES - 12 // size(uint64) + id(uint32)
196                             - (resultSize % ApkUtil.ANDROID_COMMON_PAGE_ALIGNMENT_BYTES);
197                     if (padding < 0) {
198                         padding += ApkUtil.ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
199                     }
200                     final ByteBuffer dummy =  ByteBuffer.allocate(padding).order(ByteOrder.LITTLE_ENDIAN);
201                     apkSigningBlock.addPayload(new ApkSigningPayload(ApkUtil.VERITY_PADDING_BLOCK_ID,dummy));
202                 }
203             }
204 
205             if (apkSigningBlockOffset != 0 && centralDirStartOffset != 0) {
206 
207                 // read CentralDir
208                 fIn.seek(centralDirStartOffset);
209 
210                 byte[] centralDirBytes = null;
211                 File tempCentralBytesFile = null;
212                 // read CentralDir
213                 if (lowMemory) {
214                     tempCentralBytesFile = new File(apkFile.getParent(), UUID.randomUUID().toString());
215                     FileOutputStream outStream = null;
216                     try {
217                         outStream = new FileOutputStream(tempCentralBytesFile);
218                         final byte[] buffer = new byte[1024];
219 
220                         int len;
221                         while ((len = fIn.read(buffer)) > 0){
222                             outStream.write(buffer, 0, len);
223                         }
224                     } finally {
225                         if (outStream != null) {
226                             outStream.close();
227                         }
228                     }
229                 } else {
230                     centralDirBytes = new byte[(int) (fileChannel.size() - centralDirStartOffset)];
231                     fIn.read(centralDirBytes);
232                 }
233 
234                 //update apk sign
235                 fileChannel.position(apkSigningBlockOffset);
236                 final long length = apkSigningBlock.writeApkSigningBlock(fIn);
237 
238                 // update CentralDir
239                 if (lowMemory) {
240                     FileInputStream inputStream = null;
241                     try {
242                         inputStream = new FileInputStream(tempCentralBytesFile);
243                         final byte[] buffer = new byte[1024];
244 
245                         int len;
246                         while ((len = inputStream.read(buffer)) > 0){
247                             fIn.write(buffer, 0, len);
248                         }
249                     } finally {
250                         if (inputStream != null) {
251                             inputStream.close();
252                         }
253                         tempCentralBytesFile.delete();
254                     }
255                 } else {
256                     // store CentralDir
257                     fIn.write(centralDirBytes);
258                 }
259                 // update length
260                 fIn.setLength(fIn.getFilePointer());
261 
262                 // update CentralDir Offset
263 
264                 // End of central directory record (EOCD)
265                 // Offset     Bytes     Description[23]
266                 // 0            4       End of central directory signature = 0x06054b50
267                 // 4            2       Number of this disk
268                 // 6            2       Disk where central directory starts
269                 // 8            2       Number of central directory records on this disk
270                 // 10           2       Total number of central directory records
271                 // 12           4       Size of central directory (bytes)
272                 // 16           4       Offset of start of central directory, relative to start of archive
273                 // 20           2       Comment length (n)
274                 // 22           n       Comment
275 
276                 fIn.seek(fileChannel.size() - commentLength - 6);
277                 // 6 = 2(Comment length) + 4 (Offset of start of central directory, relative to start of archive)
278                 final ByteBuffer temp = ByteBuffer.allocate(4);
279                 temp.order(ByteOrder.LITTLE_ENDIAN);
280                 temp.putInt((int) (centralDirStartOffset + length + 8 - (centralDirStartOffset - apkSigningBlockOffset)));
281                 // 8 = size of block in bytes (excluding this field) (uint64)
282                 temp.flip();
283                 fIn.write(temp.array());
284 
285             }
286         } finally {
287             if (fileChannel != null) {
288                 fileChannel.close();
289             }
290             if (fIn != null) {
291                 fIn.close();
292             }
293         }
294     }
295     
296     public static void main(String[] args) {
297         try {
298             put(new File("C:\Users\Plum\Downloads\app.apk"), ApkUtil.APK_CHANNEL_BLOCK_ID,"附加信息");
299         } catch (IOException | SignatureNotFoundException e) {
300             e.printStackTrace();
301         }
302     }
303 }

从APK中读取信息:

  1 import java.io.File;
  2 import java.io.IOException;
  3 import java.io.RandomAccessFile;
  4 import java.io.UnsupportedEncodingException;
  5 import java.nio.ByteBuffer;
  6 import java.nio.channels.FileChannel;
  7 import java.util.Arrays;
  8 import java.util.Map;
  9 
 10 public final class PayloadReader {
 11     private PayloadReader() {
 12         super();
 13     }
 14 
 15     /**
 16      * get string (UTF-8) by id
 17      *
 18      * @param apkFile apk file
 19      * @return null if not found
 20      */
 21     public static String getString(final File apkFile, final int id) {
 22         final byte[] bytes = PayloadReader.get(apkFile, id);
 23         if (bytes == null) {
 24             return null;
 25         }
 26         try {
 27             return new String(bytes, ApkUtil.DEFAULT_CHARSET);
 28         } catch (UnsupportedEncodingException e) {
 29             e.printStackTrace();
 30         }
 31         return null;
 32     }
 33 
 34     /**
 35      * get bytes by id <br/>
 36      *
 37      * @param apkFile apk file
 38      * @param id      id
 39      * @return bytes
 40      */
 41     public static byte[] get(final File apkFile, final int id) {
 42         final Map<Integer, ByteBuffer> idValues = getAll(apkFile);
 43         if (idValues == null) {
 44             return null;
 45         }
 46         final ByteBuffer byteBuffer = idValues.get(id);
 47         if (byteBuffer == null) {
 48             return null;
 49         }
 50         return getBytes(byteBuffer);
 51     }
 52 
 53     /**
 54      * get data from byteBuffer
 55      *
 56      * @param byteBuffer buffer
 57      * @return useful data
 58      */
 59     private static byte[] getBytes(final ByteBuffer byteBuffer) {
 60         final byte[] array = byteBuffer.array();
 61         final int arrayOffset = byteBuffer.arrayOffset();
 62         return Arrays.copyOfRange(array, arrayOffset + byteBuffer.position(),
 63                 arrayOffset + byteBuffer.limit());
 64     }
 65 
 66     /**
 67      * get all custom (id, buffer) <br/>
 68      * Note: get final from byteBuffer, please use {@link PayloadReader#getBytes getBytes}
 69      *
 70      * @param apkFile apk file
 71      * @return all custom (id, buffer)
 72      */
 73     private static Map<Integer, ByteBuffer> getAll(final File apkFile) {
 74         Map<Integer, ByteBuffer> idValues = null;
 75         try {
 76             RandomAccessFile randomAccessFile = null;
 77             FileChannel fileChannel = null;
 78             try {
 79                 randomAccessFile = new RandomAccessFile(apkFile, "r");
 80                 fileChannel = randomAccessFile.getChannel();
 81                 final ByteBuffer apkSigningBlock2 = ApkUtil.findApkSigningBlock(fileChannel).getFirst();
 82                 idValues = ApkUtil.findIdValues(apkSigningBlock2);
 83             } catch (IOException ignore) {
 84             } finally {
 85                 try {
 86                     if (fileChannel != null) {
 87                         fileChannel.close();
 88                     }
 89                 } catch (IOException ignore) {
 90                 }
 91                 try {
 92                     if (randomAccessFile != null) {
 93                         randomAccessFile.close();
 94                     }
 95                 } catch (IOException ignore) {
 96                 }
 97             }
 98         } catch (SignatureNotFoundException ignore) {
 99         }
100 
101         return idValues;
102     }
103 
104 
105 }

下面是一些辅助类:

 1 /**
 2  * Pair of two elements.
 3  */
 4 final class Pair<A, B> {
 5     private final A mFirst;
 6     private final B mSecond;
 7 
 8     private Pair(final A first, final B second) {
 9         mFirst = first;
10         mSecond = second;
11     }
12 
13     public static <A, B> Pair<A, B> of(final A first, final B second) {
14         return new Pair<A, B>(first, second);
15     }
16 
17     public A getFirst() {
18         return mFirst;
19     }
20 
21     public B getSecond() {
22         return mSecond;
23     }
24 
25     @Override
26     public int hashCode() {
27         final int prime = 31;
28         int result = 1;
29         result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode());
30         result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode());
31         return result;
32     }
33 
34     @Override
35     public boolean equals(final Object obj) {
36         if (this == obj) {
37             return true;
38         }
39         if (obj == null) {
40             return false;
41         }
42         if (getClass() != obj.getClass()) {
43             return false;
44         }
45         @SuppressWarnings("rawtypes")
46         final Pair other = (Pair) obj;
47         if (mFirst == null) {
48             if (other.mFirst != null) {
49                 return false;
50             }
51         } else if (!mFirst.equals(other.mFirst)) {
52             return false;
53         }
54         if (mSecond == null) {
55             if (other.mSecond != null) {
56                 return false;
57             }
58         } else if (!mSecond.equals(other.mSecond)) {
59             return false;
60         }
61         return true;
62     }
63 }
View Code
public class SignatureNotFoundException extends Exception {
    private static final long serialVersionUID = 1L;

    public SignatureNotFoundException(final String message) {
        super(message);
    }

    public SignatureNotFoundException(final String message, final Throwable cause) {
        super(message, cause);
    }
}
View Code
  1 import java.io.DataOutput;
  2 import java.io.IOException;
  3 import java.nio.ByteBuffer;
  4 import java.nio.ByteOrder;
  5 import java.util.ArrayList;
  6 import java.util.List;
  7 
  8 /**
  9  * https://source.android.com/security/apksigning/v2.html
 10  * https://en.wikipedia.org/wiki/Zip_(file_format)
 11  */
 12 class ApkSigningBlock {
 13     // The format of the APK Signing Block is as follows (all numeric fields are little-endian):
 14 
 15     // .size of block in bytes (excluding this field) (uint64)
 16     // .Sequence of uint64-length-prefixed ID-value pairs:
 17     //   *ID (uint32)
 18     //   *value (variable-length: length of the pair - 4 bytes)
 19     // .size of block in bytes—same as the very first field (uint64)
 20     // .magic “APK Sig Block 42” (16 bytes)
 21 
 22     // FORMAT:
 23     // OFFSET       DATA TYPE  DESCRIPTION
 24     // * @+0  bytes uint64:    size in bytes (excluding this field)
 25     // * @+8  bytes payload
 26     // * @-24 bytes uint64:    size in bytes (same as the one above)
 27     // * @-16 bytes uint128:   magic
 28 
 29     // payload 有 8字节的大小,4字节的ID,还有payload的内容组成
 30 
 31     private final List<ApkSigningPayload> payloads;
 32 
 33     ApkSigningBlock() {
 34         super();
 35 
 36         payloads = new ArrayList<ApkSigningPayload>();
 37     }
 38 
 39     public final List<ApkSigningPayload> getPayloads() {
 40         return payloads;
 41     }
 42 
 43     public void addPayload(final ApkSigningPayload payload) {
 44         payloads.add(payload);
 45     }
 46 
 47     public long writeApkSigningBlock(final DataOutput dataOutput) throws IOException {
 48         long length = 24; // 24 = 8(size of block in bytes—same as the very first field (uint64)) + 16 (magic “APK Sig Block 42” (16 bytes))
 49         for (int index = 0; index < payloads.size(); ++index) {
 50             final ApkSigningPayload payload = payloads.get(index);
 51             final byte[] bytes = payload.getByteBuffer();
 52             length += 12 + bytes.length; // 12 = 8(uint64-length-prefixed) + 4 (ID (uint32))
 53         }
 54 
 55         ByteBuffer byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
 56         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
 57         byteBuffer.putLong(length);
 58         byteBuffer.flip();
 59         dataOutput.write(byteBuffer.array());
 60 
 61         for (int index = 0; index < payloads.size(); ++index) {
 62             final ApkSigningPayload payload = payloads.get(index);
 63             final byte[] bytes = payload.getByteBuffer();
 64 
 65             byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
 66             byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
 67             byteBuffer.putLong(bytes.length + (8 - 4)); // Long.BYTES - Integer.BYTES
 68             byteBuffer.flip();
 69             dataOutput.write(byteBuffer.array());
 70 
 71             byteBuffer = ByteBuffer.allocate(4); // Integer.BYTES
 72             byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
 73             byteBuffer.putInt(payload.getId());
 74             byteBuffer.flip();
 75             dataOutput.write(byteBuffer.array());
 76 
 77             dataOutput.write(bytes);
 78         }
 79 
 80         byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
 81         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
 82         byteBuffer.putLong(length);
 83         byteBuffer.flip();
 84         dataOutput.write(byteBuffer.array());
 85 
 86         byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
 87         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
 88         byteBuffer.putLong(ApkUtil.APK_SIG_BLOCK_MAGIC_LO);
 89         byteBuffer.flip();
 90         dataOutput.write(byteBuffer.array());
 91 
 92         byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
 93         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
 94         byteBuffer.putLong(ApkUtil.APK_SIG_BLOCK_MAGIC_HI);
 95         byteBuffer.flip();
 96         dataOutput.write(byteBuffer.array());
 97 
 98         return length;
 99     }
100 }
View Code
 1 import java.nio.ByteBuffer;
 2 import java.nio.ByteOrder;
 3 import java.util.Arrays;
 4 
 5 class ApkSigningPayload {
 6     private final int id;
 7     private final ByteBuffer buffer;
 8     private final int totalSize;
 9 
10     ApkSigningPayload(final int id, final ByteBuffer buffer) {
11         super();
12         this.id = id;
13         if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
14             throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
15         }
16         this.buffer = buffer;
17         // assume buffer is not consumed
18         this.totalSize = 8 + 4 + buffer.remaining(); // size + id + value
19     }
20 
21     public int getId() {
22         return id;
23     }
24 
25     public byte[] getByteBuffer() {
26         final byte[] array = buffer.array();
27         final int arrayOffset = buffer.arrayOffset();
28         return Arrays.copyOfRange(array, arrayOffset + buffer.position(),
29                 arrayOffset + buffer.limit());
30     }
31 
32     /**
33      * Total bytes of this block
34      */
35     public int getTotalSize() {
36         return totalSize;
37     }
38 }
View Code
  1 import java.io.IOException;
  2 import java.nio.BufferUnderflowException;
  3 import java.nio.ByteBuffer;
  4 import java.nio.ByteOrder;
  5 import java.nio.channels.FileChannel;
  6 import java.util.LinkedHashMap;
  7 import java.util.Map;
  8 
  9 final class ApkUtil {
 10     private ApkUtil() {
 11         super();
 12     }
 13 
 14     /**
 15      * APK Signing Block Magic Code: magic “APK Sig Block 42” (16 bytes)
 16      * "APK Sig Block 42" : 41 50 4B 20 53 69 67 20 42 6C 6F 63 6B 20 34 32
 17      */
 18     public static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L; // LITTLE_ENDIAN, High
 19     public static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L; // LITTLE_ENDIAN, Low
 20     private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
 21 
 22     /*
 23      The v2 signature of the APK is stored as an ID-value pair with ID 0x7109871a
 24      (https://source.android.com/security/apksigning/v2.html#apk-signing-block)
 25       */
 26     public static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
 27 
 28     /**
 29      * The padding in APK SIG BLOCK (V3 scheme introduced)
 30      * See https://android.googlesource.com/platform/tools/apksig/+/master/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java
 31      */
 32     public static final int VERITY_PADDING_BLOCK_ID = 0x42726577;
 33 
 34     public static final int ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096;
 35 
 36 
 37     // Our Channel Block ID
 38     public static final int APK_CHANNEL_BLOCK_ID = 0x71777777;
 39 
 40     public static final String DEFAULT_CHARSET = "UTF-8";
 41 
 42     private static final int ZIP_EOCD_REC_MIN_SIZE = 22;
 43     private static final int ZIP_EOCD_REC_SIG = 0x06054b50;
 44     private static final int UINT16_MAX_VALUE = 0xffff;
 45     private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20;
 46 
 47     public static long getCommentLength(final FileChannel fileChannel) throws IOException {
 48         // End of central directory record (EOCD)
 49         // Offset    Bytes     Description[23]
 50         // 0           4       End of central directory signature = 0x06054b50
 51         // 4           2       Number of this disk
 52         // 6           2       Disk where central directory starts
 53         // 8           2       Number of central directory records on this disk
 54         // 10          2       Total number of central directory records
 55         // 12          4       Size of central directory (bytes)
 56         // 16          4       Offset of start of central directory, relative to start of archive
 57         // 20          2       Comment length (n)
 58         // 22          n       Comment
 59         // For a zip with no archive comment, the
 60         // end-of-central-directory record will be 22 bytes long, so
 61         // we expect to find the EOCD marker 22 bytes from the end.
 62 
 63 
 64         final long archiveSize = fileChannel.size();
 65         if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) {
 66             throw new IOException("APK too small for ZIP End of Central Directory (EOCD) record");
 67         }
 68         // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
 69         // The record can be identified by its 4-byte signature/magic which is located at the very
 70         // beginning of the record. A complication is that the record is variable-length because of
 71         // the comment field.
 72         // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
 73         // end of the buffer for the EOCD record signature. Whenever we find a signature, we check
 74         // the candidate record's comment length is such that the remainder of the record takes up
 75         // exactly the remaining bytes in the buffer. The search is bounded because the maximum
 76         // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.
 77         final long maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE);
 78         final long eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE;
 79         for (int expectedCommentLength = 0; expectedCommentLength <= maxCommentLength;
 80              expectedCommentLength++) {
 81             final long eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength;
 82 
 83             final ByteBuffer byteBuffer = ByteBuffer.allocate(4);
 84             fileChannel.position(eocdStartPos);
 85             fileChannel.read(byteBuffer);
 86             byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
 87 
 88             if (byteBuffer.getInt(0) == ZIP_EOCD_REC_SIG) {
 89                 final ByteBuffer commentLengthByteBuffer = ByteBuffer.allocate(2);
 90                 fileChannel.position(eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET);
 91                 fileChannel.read(commentLengthByteBuffer);
 92                 commentLengthByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
 93 
 94                 final int actualCommentLength = commentLengthByteBuffer.getShort(0);
 95                 if (actualCommentLength == expectedCommentLength) {
 96                     return actualCommentLength;
 97                 }
 98             }
 99         }
100         throw new IOException("ZIP End of Central Directory (EOCD) record not found");
101     }
102 
103     public static long findCentralDirStartOffset(final FileChannel fileChannel) throws IOException {
104         return findCentralDirStartOffset(fileChannel, getCommentLength(fileChannel));
105     }
106 
107     public static long findCentralDirStartOffset(final FileChannel fileChannel, final long commentLength) throws IOException {
108         // End of central directory record (EOCD)
109         // Offset    Bytes     Description[23]
110         // 0           4       End of central directory signature = 0x06054b50
111         // 4           2       Number of this disk
112         // 6           2       Disk where central directory starts
113         // 8           2       Number of central directory records on this disk
114         // 10          2       Total number of central directory records
115         // 12          4       Size of central directory (bytes)
116         // 16          4       Offset of start of central directory, relative to start of archive
117         // 20          2       Comment length (n)
118         // 22          n       Comment
119         // For a zip with no archive comment, the
120         // end-of-central-directory record will be 22 bytes long, so
121         // we expect to find the EOCD marker 22 bytes from the end.
122 
123         final ByteBuffer zipCentralDirectoryStart = ByteBuffer.allocate(4);
124         zipCentralDirectoryStart.order(ByteOrder.LITTLE_ENDIAN);
125         fileChannel.position(fileChannel.size() - commentLength - 6); // 6 = 2 (Comment length) + 4 (Offset of start of central directory, relative to start of archive)
126         fileChannel.read(zipCentralDirectoryStart);
127         final long centralDirStartOffset = zipCentralDirectoryStart.getInt(0);
128         return centralDirStartOffset;
129     }
130 
131     public static Pair<ByteBuffer, Long> findApkSigningBlock(
132             final FileChannel fileChannel) throws IOException, SignatureNotFoundException {
133         final long centralDirOffset = findCentralDirStartOffset(fileChannel);
134         return findApkSigningBlock(fileChannel, centralDirOffset);
135     }
136 
137     public static Pair<ByteBuffer, Long> findApkSigningBlock(
138             final FileChannel fileChannel, final long centralDirOffset) throws IOException, SignatureNotFoundException {
139 
140         // Find the APK Signing Block. The block immediately precedes the Central Directory.
141 
142         // FORMAT:
143         // OFFSET       DATA TYPE  DESCRIPTION
144         // * @+0  bytes uint64:    size in bytes (excluding this field)
145         // * @+8  bytes payload
146         // * @-24 bytes uint64:    size in bytes (same as the one above)
147         // * @-16 bytes uint128:   magic
148 
149         if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
150             throw new SignatureNotFoundException(
151                     "APK too small for APK Signing Block. ZIP Central Directory offset: "
152                             + centralDirOffset);
153         }
154         // Read the magic and offset in file from the footer section of the block:
155         // * uint64:   size of block
156         // * 16 bytes: magic
157         fileChannel.position(centralDirOffset - 24);
158         final ByteBuffer footer = ByteBuffer.allocate(24);
159         fileChannel.read(footer);
160         footer.order(ByteOrder.LITTLE_ENDIAN);
161         if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
162                 || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
163             throw new SignatureNotFoundException(
164                     "No APK Signing Block before ZIP Central Directory");
165         }
166         // Read and compare size fields
167         final long apkSigBlockSizeInFooter = footer.getLong(0);
168         if ((apkSigBlockSizeInFooter < footer.capacity())
169                 || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
170             throw new SignatureNotFoundException(
171                     "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
172         }
173         final int totalSize = (int) (apkSigBlockSizeInFooter + 8);
174         final long apkSigBlockOffset = centralDirOffset - totalSize;
175         if (apkSigBlockOffset < 0) {
176             throw new SignatureNotFoundException(
177                     "APK Signing Block offset out of range: " + apkSigBlockOffset);
178         }
179         fileChannel.position(apkSigBlockOffset);
180         final ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
181         fileChannel.read(apkSigBlock);
182         apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
183         final long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
184         if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
185             throw new SignatureNotFoundException(
186                     "APK Signing Block sizes in header and footer do not match: "
187                             + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
188         }
189         return Pair.of(apkSigBlock, apkSigBlockOffset);
190     }
191 
192     public static Map<Integer, ByteBuffer> findIdValues(final ByteBuffer apkSigningBlock) throws SignatureNotFoundException {
193         checkByteOrderLittleEndian(apkSigningBlock);
194         // FORMAT:
195         // OFFSET       DATA TYPE  DESCRIPTION
196         // * @+0  bytes uint64:    size in bytes (excluding this field)
197         // * @+8  bytes pairs
198         // * @-24 bytes uint64:    size in bytes (same as the one above)
199         // * @-16 bytes uint128:   magic
200         final ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
201 
202         final Map<Integer, ByteBuffer> idValues = new LinkedHashMap<Integer, ByteBuffer>(); // keep order
203 
204         int entryCount = 0;
205         while (pairs.hasRemaining()) {
206             entryCount++;
207             if (pairs.remaining() < 8) {
208                 throw new SignatureNotFoundException(
209                         "Insufficient data to read size of APK Signing Block entry #" + entryCount);
210             }
211             final long lenLong = pairs.getLong();
212             if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
213                 throw new SignatureNotFoundException(
214                         "APK Signing Block entry #" + entryCount
215                                 + " size out of range: " + lenLong);
216             }
217             final int len = (int) lenLong;
218             final int nextEntryPos = pairs.position() + len;
219             if (len > pairs.remaining()) {
220                 throw new SignatureNotFoundException(
221                         "APK Signing Block entry #" + entryCount + " size out of range: " + len
222                                 + ", available: " + pairs.remaining());
223             }
224             final int id = pairs.getInt();
225             idValues.put(id, getByteBuffer(pairs, len - 4));
226 
227             pairs.position(nextEntryPos);
228         }
229 
230         return idValues;
231     }
232 
233     /**
234      * Returns new byte buffer whose content is a shared subsequence of this buffer's content
235      * between the specified start (inclusive) and end (exclusive) positions. As opposed to
236      * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
237      * buffer's byte order.
238      */
239     private static ByteBuffer sliceFromTo(final ByteBuffer source, final int start, final int end) {
240         if (start < 0) {
241             throw new IllegalArgumentException("start: " + start);
242         }
243         if (end < start) {
244             throw new IllegalArgumentException("end < start: " + end + " < " + start);
245         }
246         final int capacity = source.capacity();
247         if (end > source.capacity()) {
248             throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
249         }
250         final int originalLimit = source.limit();
251         final int originalPosition = source.position();
252         try {
253             source.position(0);
254             source.limit(end);
255             source.position(start);
256             final ByteBuffer result = source.slice();
257             result.order(source.order());
258             return result;
259         } finally {
260             source.position(0);
261             source.limit(originalLimit);
262             source.position(originalPosition);
263         }
264     }
265 
266     /**
267      * Relative <em>get</em> method for reading {@code size} number of bytes from the current
268      * position of this buffer.
269      * <p>
270      * <p>This method reads the next {@code size} bytes at this buffer's current position,
271      * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
272      * {@code size}, byte order set to this buffer's byte order; and then increments the position by
273      * {@code size}.
274      */
275     private static ByteBuffer getByteBuffer(final ByteBuffer source, final int size)
276             throws BufferUnderflowException {
277         if (size < 0) {
278             throw new IllegalArgumentException("size: " + size);
279         }
280         final int originalLimit = source.limit();
281         final int position = source.position();
282         final int limit = position + size;
283         if ((limit < position) || (limit > originalLimit)) {
284             throw new BufferUnderflowException();
285         }
286         source.limit(limit);
287         try {
288             final ByteBuffer result = source.slice();
289             result.order(source.order());
290             source.position(limit);
291             return result;
292         } finally {
293             source.limit(originalLimit);
294         }
295     }
296 
297     private static void checkByteOrderLittleEndian(final ByteBuffer buffer) {
298         if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
299             throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
300         }
301     }
302 
303 
304 }
View Code

总结

  可以看出,现在高版本安卓对安装包的校验很严格,所以,普通的替换配置文件决定不行的,一替换,就破坏签名了,是我太年轻。直接往安装包的comments区写信息,以前可行,但现在也不行了。美团的这个工具,我也没看的很透很懂,只是知道他的原理还是往comments区添加信息,只是添加过程中对安装包的签名信息做了一些处理。

原文地址:https://www.cnblogs.com/plumsq/p/11589776.html