V2签名多渠道脚本与渠道读取

笔记,避免忘记和丢失

签名脚本

import java.nio.BufferUnderflowException
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.channels.FileChannel
/**
 * 多渠道写入脚本
 * Created by zhangjianliang on 2018/12/28
 */
//多渠道打包相关配置
// 发布文件夹 (绝对路径)
def packageLocPath = "/var/lib/jenkins/workspace/App_dir/outputs"
def windowsPackagePath = "${rootProject.rootDir}/outputs"
// 最终发布包存放的子目录
def childPath = "publish"
// 打包日期
def releaseTime = new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+8"))
// 发布包前缀
def baseAppName = "appName"
// 渠道文件名
def channelFileName = "channel.txt"
// 渠道名:渠道值
def packageChannelMap = [:]

def DEFAULT_CHARSET = "UTF-8"
/*
 The v2 signature of the APK is stored as an ID-value pair with ID 0x7109871a
 (https://source.android.com/security/apksigning/v2.html#apk-signing-block)
 */
def APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
// Our Channel Block ID
def APK_CHANNEL_BLOCK_ID = 0x71777777;

/**
 * APK Signing Block Magic Code: magic “APK Sig Block 42” (16 bytes)
 * "APK Sig Block 42" : 41 50 4B 20 53 69 67 20 42 6C 6F 63 6B 20 34 32
 */
def APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L; // LITTLE_ENDIAN, High
def APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L; // LITTLE_ENDIAN, Low
def APK_SIG_BLOCK_MIN_SIZE = 32;

def ZIP_EOCD_REC_MIN_SIZE = 22;
def ZIP_EOCD_REC_SIG = 0x06054b50;
def UINT16_MAX_VALUE = 0xffff;
def ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20;

// The format of the APK Signing Block is as follows (all numeric fields are little-endian):

// .size of block in bytes (excluding this field) (uint64)
// .Sequence of uint64-length-prefixed ID-value pairs:
//   *ID (uint32)
//   *value (variable-length: length of the pair - 4 bytes)
// .size of block in bytes—same as the very first field (uint64)
// .magic “APK Sig Block 42” (16 bytes)

// FORMAT:
// OFFSET       DATA TYPE  DESCRIPTION
// * @+0  bytes uint64:    size in bytes (excluding this field)
// * @+8  bytes payload
// * @-24 bytes uint64:    size in bytes (same as the one above)
// * @-16 bytes uint128:   magic
// payload 有 8字节的大小,4字节的ID,还有payload的内容组成
def payloads = [];

// 读取渠道值文件闭包 "渠道名:渠道值" 一行一个渠道
def readChannelFromFile = { String path ->
    def channelMap = [:]
    new File(path).eachLine {
        String[] channelStr = it.split(":")
        def channelName = channelStr[0].trim()
        def value = "";
        if (channelStr.length > 1) {
            value = it.split(":")[1].trim()
        }
        channelMap[channelName] = value
    }
    return channelMap
}

// 准备渠道 key-value
packageChannelMap = readChannelFromFile("${rootDir}/${channelFileName}")

// 判断是否为 Windows 系统
def isWindowsOS = {
    return System.getProperty("os.name").toLowerCase().indexOf("windows") >= 0
}

// 判断是否为 Mac 系统
def isMacOS = {
    return System.getProperty("os.name").toLowerCase().indexOf("mac") >= 0
}

/**
 * Relative <em>get</em> method for reading {@code size} number of bytes from the current
 * position of this buffer.
 * <p>
 * <p>This method reads the next {@code size} bytes at this buffer's current position,
 * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
 * {@code size}, byte order set to this buffer's byte order; and then increments the position by
 * {@code size}.
 */
def getByteBuffer = { final ByteBuffer source, final int size ->
    if (size < 0) {
        throw new IllegalArgumentException("size: " + size);
    }
    final int originalLimit = source.limit();
    final int position = source.position();
    final int limit = position + size;
    if ((limit < position) || (limit > originalLimit)) {
        throw new BufferUnderflowException();
    }
    source.limit(limit);
    try {
        final ByteBuffer result = source.slice();
        result.order(source.order());
        source.position(limit);
        return result;
    } finally {
        source.limit(originalLimit);
    }
}

/**
 * Returns new byte buffer whose content is a shared subsequence of this buffer's content
 * between the specified start (inclusive) and end (exclusive) positions. As opposed to
 * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
 * buffer's byte order.
 */
def sliceFromTo = { final ByteBuffer source, final int start, final int end ->
    if (start < 0) {
        throw new IllegalArgumentException("start: " + start);
    }
    if (end < start) {
        throw new IllegalArgumentException("end < start: " + end + " < " + start);
    }
    final int capacity = source.capacity();
    if (end > source.capacity()) {
        throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
    }
    final int originalLimit = source.limit();
    final int originalPosition = source.position();
    try {
        source.position(0);
        source.limit(end);
        source.position(start);
        final ByteBuffer result = source.slice();
        result.order(source.order());
        return result;
    } finally {
        source.position(0);
        source.limit(originalLimit);
        source.position(originalPosition);
    }
}

/**
 * https://source.android.com/security/apksigning/v2.html
 * https://en.wikipedia.org/wiki/Zip_(file_format)
 */
def writeApkSigningBlock = { DataOutput dataOutput ->
    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))
    for (int index = 0; index < payloads.size(); ++index) {
        final ApkSigningPayload payload = payloads.get(index);
        final byte[] bytes = payload.getByteBuffer();
        length += 12 + bytes.length; // 12 = 8(uint64-length-prefixed) + 4 (ID (uint32))
    }

    ByteBuffer byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
    byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
    byteBuffer.putLong(length);
    byteBuffer.flip();
    dataOutput.write(byteBuffer.array());

    for (int index = 0; index < payloads.size(); ++index) {
        final ApkSigningPayload payload = payloads.get(index);
        final byte[] bytes = payload.getByteBuffer();

        byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
        byteBuffer.putLong(bytes.length + (8 - 4)); // Long.BYTES - Integer.BYTES
        byteBuffer.flip();
        dataOutput.write(byteBuffer.array());

        byteBuffer = ByteBuffer.allocate(4); // Integer.BYTES
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
        byteBuffer.putInt(payload.getId());
        byteBuffer.flip();
        dataOutput.write(byteBuffer.array());

        dataOutput.write(bytes);
    }

    byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
    byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
    byteBuffer.putLong(length);
    byteBuffer.flip();
    dataOutput.write(byteBuffer.array());

    byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
    byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
    byteBuffer.putLong(APK_SIG_BLOCK_MAGIC_LO);
    byteBuffer.flip();
    dataOutput.write(byteBuffer.array());

    byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
    byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
    byteBuffer.putLong(APK_SIG_BLOCK_MAGIC_HI);
    byteBuffer.flip();
    dataOutput.write(byteBuffer.array());

    return length;
}

def findCentralDirStartOffset = { final FileChannel fileChannel, final long commentLength ->
    // End of central directory record (EOCD)
    // Offset    Bytes     Description[23]
    // 0           4       End of central directory signature = 0x06054b50
    // 4           2       Number of this disk
    // 6           2       Disk where central directory starts
    // 8           2       Number of central directory records on this disk
    // 10          2       Total number of central directory records
    // 12          4       Size of central directory (bytes)
    // 16          4       Offset of start of central directory, relative to start of archive
    // 20          2       Comment length (n)
    // 22          n       Comment
    // For a zip with no archive comment, the
    // end-of-central-directory record will be 22 bytes long, so
    // we expect to find the EOCD marker 22 bytes from the end.

    final ByteBuffer zipCentralDirectoryStart = ByteBuffer.allocate(4);
    zipCentralDirectoryStart.order(ByteOrder.LITTLE_ENDIAN);
    fileChannel.position(fileChannel.size() - commentLength - 6);
    // 6 = 2 (Comment length) + 4 (Offset of start of central directory, relative to start of archive)
    fileChannel.read(zipCentralDirectoryStart);
    final long centralDirStartOffset = zipCentralDirectoryStart.getInt(0);
    return centralDirStartOffset;
}

def getCommentLength = { final FileChannel fileChannel ->
    // End of central directory record (EOCD)
    // Offset    Bytes     Description[23]
    // 0           4       End of central directory signature = 0x06054b50
    // 4           2       Number of this disk
    // 6           2       Disk where central directory starts
    // 8           2       Number of central directory records on this di
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值