首页 > 解决方案 > 解密问题,android AES/CTR/NoPadding 中的密码

问题描述

当我在 android Marshmallow(Android 6.0.1)上使用此代码时,解密是好的,但是当我在带有 android Oreo(Android 8)的设备上运行时,解密值不一样并且数据不正确。

private void decrypt(Cipher cipher, Uri uri) throws Exception {
    long a = 113845229;
    InputStream inputStream = getContentResolver().openInputStream(uri);
    CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
    cipherInputStream.skip(a);
    byte[] buffer = new byte[8];
    cipherInputStream.read(buffer);
}

// create cipher
private Cipher createCipher(byte[] iv, byte[] salt, String password) throws Exception {
    IvParameterSpec mIvParameterSpec = new IvParameterSpec(iv);
    SecretKeySpec mSecretKeySpec = generate(password, salt);
    Cipher mCipher = Cipher.getInstance("AES/CTR/NoPadding");
    mCipher.init(Cipher.DECRYPT_MODE, mSecretKeySpec, mIvParameterSpec);
    return mCipher;
}
// generate key
private SecretKeySpec generate(String password, byte[] salt) throws Exception {
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    md.update(salt);
    byte[] key = md.digest(password.getBytes(StandardCharsets.UTF_8));
    return new SecretKeySpec(key, "AES");
}

缓冲区数据在 android 6 中没问题,但在 android 8 中数据不正确。

标签: javaandroidencryptioninputstreamandroid-version

解决方案


我相信您正在寻找对ctr 加密数据的随机访问;CipherInputStream 中的 Skip 方法并没有这样做,并且是“独立于 Android 版本”(仍在使用;自 api 级别1以来未弃用或替换!);

看一下 CipherInputStream 类文件;它的内部属性很少:

private Cipher cipher;//the cipher you pass to constructor;
// the underlying input stream
private InputStream input;
/* the buffer holding data that have been read in from the
   underlying stream, but have not been processed by the cipher
   engine. the size 512 bytes is somewhat randomly chosen */
private byte[] ibuffer = new byte[512];//holds encrypted data
// having reached the end of the underlying input stream
private boolean done = false;
/* the buffer holding data that have been processed by the cipher
   engine, but have not been read out */
private byte[] obuffer;//a portion of data that's decrypted but not yet read;
// the offset pointing to the next "new" byte
private int ostart = 0;
// the offset pointing to the last "new" byte
private int ofinish = 0;

这就是 skip 在 CipherInputStream 中所做的;

public long skip(long n) throws IOException {
    int available = ofinish - ostart;
    if (n > available) {
        n = available;
    }
    if (n < 0) {
        return 0;
    }
    ostart += n;
    return n;
}

它不会将新数据加载到缓冲区或 ibuffer;它只跳过缓冲区中可用的内容(只是增加 ostart);

这应该可以做到(有改进的余地):

private static IvParameterSpec calculateIVForOffset(final IvParameterSpec iv,
    final long blockOffset) {
    final BigInteger ivBI = new BigInteger(1, iv.getIV());
    final BigInteger ivForOffsetBI = ivBI.add(BigInteger.valueOf(blockOffset
        / AES_BLOCK_SIZE));

    final byte[] ivForOffsetBA = ivForOffsetBI.toByteArray();
    final IvParameterSpec ivForOffset;
    if (ivForOffsetBA.length >= AES_BLOCK_SIZE) {
    ivForOffset = new IvParameterSpec(ivForOffsetBA, ivForOffsetBA.length - AES_BLOCK_SIZE,
            AES_BLOCK_SIZE);
    } else {
        final byte[] ivForOffsetBASized = new byte[AES_BLOCK_SIZE];
        System.arraycopy(ivForOffsetBA, 0, ivForOffsetBASized, AES_BLOCK_SIZE
            - ivForOffsetBA.length, ivForOffsetBA.length);
        ivForOffset = new IvParameterSpec(ivForOffsetBASized);
    }
    return ivForOffset;
}
long offset = 113845229;// aka a
private void decrypt(Cipher cipher, Uri uri) throws Exception {
    long skip_this_much=offset-(offset%16);
    InputStream inputStream = getContentResolver().openInputStream(uri);
    do{
        skip_this_much=skip_this_much-inputStream.skip(skip_this_much);//InputStream.skip does not necessarily skip as much as specified in parameter and returns the actually skipped value;
    }while(skip_this_much!=0);//not there yet; keep skipping;
    CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
    int read_this_much=8;
    byte[] buffer=new byte[read_this_much+(offset%16)];
    cipherInputStream.read(buffer,0,read_this_much+(offset%16));//improve this yourself
    buffer= Arrays.copyOfRange(buffer,offset%16,read_this_much+(offset%16));
}
// create cipher for offset
private Cipher createCipher(byte[] iv, byte[] salt, String password) throws Exception {
    IvParameterSpec mIvParameterSpec = new IvParameterSpec(iv);
    SecretKeySpec mSecretKeySpec = generate(password, salt);
    Cipher mCipher = Cipher.getInstance("AES/CTR/NoPadding");
    mCipher.init(Cipher.DECRYPT_MODE, mSecretKeySpec, calculateIVForOffset(mIvParameterSpec,offset));
    return mCipher;
}
// generate key
private SecretKeySpec generate(String password, byte[] salt) throws Exception {
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    md.update(salt);
    byte[] key = md.digest(password.getBytes(StandardCharsets.UTF_8));
    return new SecretKeySpec(key, "AES");
}

推荐阅读