• 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区添加信息,只是添加过程中对安装包的签名信息做了一些处理。

  • 相关阅读:
    数据库对象命名参考
    写有效率的SQL查询(I)
    一個常用的phpclass類
    写有效率的SQL查询(IV)
    深入浅出理解索引结构
    Java初学者:Jsp开发环境配置全过程
    总结性知识:做网站的一些定律
    如何在JBuilder2007中配置MyEclipse
    幾個有用的ASP Function
    [SQL] 系统存储过程
  • 原文地址:https://www.cnblogs.com/plumsq/p/11589776.html
Copyright © 2020-2023  润新知