为了账号安全,请及时绑定邮箱和手机立即绑定

android AES/CTR/NoPadding 中的解密、密码问题

android AES/CTR/NoPadding 中的解密、密码问题

jeck猫 2023-06-14 16:18:03
当我在 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 cipherprivate 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 keyprivate 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 中数据不正确。
查看完整描述

2 回答

?
皈依舞

TA贡献1851条经验 获得超3个赞

我相信您正在寻找对 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;

}

它不会加载新数据到 obuffer 或 ibuffer;它只跳过 obuffer 中可用的内容(只是增加 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");

}


查看完整回答
反对 回复 2023-06-14
?
红颜莎娜

TA贡献1842条经验 获得超12个赞

经过研究我得出结论。你应该用特定的密码实现 InputStream。


private static final int AES_BLOCK_SIZE = 16;

private InputStream mUpstream;

private Cipher mCipher;

private SecretKeySpec mSecretKeySpec;

private IvParameterSpec mIvParameterSpec;


public StreamingCipherInputStream(InputStream inputStream, Cipher cipher, 

    SecretKeySpec secretKeySpec, IvParameterSpec ivParameterSpec) {

    super(inputStream, cipher);

    mUpstream = inputStream;

    mCipher = cipher;

    mSecretKeySpec = secretKeySpec;

    mIvParameterSpec = ivParameterSpec; }

@Override

public int read(byte[] b, int off, int len) throws IOException {

    return super.read(b, off, len);  }

public long forceSkip(long bytesToSkip) throws IOException {

    long skipped = mUpstream.skip(bytesToSkip);

    try {

        int skip = (int) (bytesToSkip % AES_BLOCK_SIZE);

        long blockOffset = bytesToSkip - skip;

        long numberOfBlocks = blockOffset / AES_BLOCK_SIZE;


        BigInteger ivForOffsetAsBigInteger = new BigInteger(1, 

        mIvParameterSpec.getIV()).add(BigInteger.valueOf(numberOfBlocks));

        byte[] ivForOffsetByteArray = ivForOffsetAsBigInteger.toByteArray();

        IvParameterSpec computedIvParameterSpecForOffset;

        if (ivForOffsetByteArray.length < AES_BLOCK_SIZE) {

            byte[] resizedIvForOffsetByteArray = new byte[AES_BLOCK_SIZE];

            System.arraycopy(ivForOffsetByteArray, 0, resizedIvForOffsetByteArray, 

            AES_BLOCK_SIZE - ivForOffsetByteArray.length, ivForOffsetByteArray.length);

            computedIvParameterSpecForOffset = new IvParameterSpec(resizedIvForOffsetByteArray);

        } else {

            computedIvParameterSpecForOffset = new IvParameterSpec(ivForOffsetByteArray, ivForOffsetByteArray.length - AES_BLOCK_SIZE, AES_BLOCK_SIZE);

        }

        mCipher.init(Cipher.ENCRYPT_MODE, mSecretKeySpec, computedIvParameterSpecForOffset);

        byte[] skipBuffer = new byte[skip];

        mCipher.update(skipBuffer, 0, skip, skipBuffer);

        Arrays.fill(skipBuffer, (byte) 0);

    } catch (Exception e) {

        return 0;

    }

    return skipped;

}

@Override

public int available() throws IOException {

    return mUpstream.available();}


查看完整回答
反对 回复 2023-06-14
  • 2 回答
  • 0 关注
  • 209 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信