Проблема с расшифровкой, шифр в андроиде 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 данные неверны.


person Ali Elahi    schedule 21.08.2019    source источник
comment
Добро пожаловать в СО. Пожалуйста, чтобы получить лучший ответ после минимального, полного, проверяемого на примере.   -  person kelalaka    schedule 21.08.2019
comment
Не могли бы вы проверить значения, которые возвращает метод skip? Это всегда опасный метод, и API оставляет возможность пропустить меньше байтов, чем запрошено. Также непонятно, что он делает с лежащим в его основе шифром: проходят ли через него байты или нет? Возможно, вам следует пропустить родительский поток.   -  person Maarten Bodewes    schedule 21.08.2019
comment
@MaartenBodewes: метод skip() работает и передает данные через шифр, но вы обязательно должны проверить возвращаемое значение. Версия OpenJDK никогда не будет пропускать более 512 байт за вызов из-за размера внутреннего буфера. Я не знаю, что делает Android, но, вероятно, это похоже.   -  person President James K. Polk    schedule 21.08.2019
comment
Лично я бы решил это, используя сопоставление памяти с файлом, а не с помощью потоков. А можно поставить счетчик в режиме CTR, грамотно переделав IV, в самом шифре ничего пропускать не надо.   -  person Maarten Bodewes    schedule 22.08.2019
comment
Я должен указать, что skip() работает в том смысле, что он правильно пропускает открытый текст, но совершенно бесполезен для вашей проблемы. Либо используйте read() и отбрасывайте байты, либо, что гораздо более эффективно, разумно измените IV в соответствии с предложением @MaartenBodewes и seek() на смещение в интересующем вас файле на границе блока (16 байт). Если поток не доступен для поиска, значит, вы застряли, используя read()   -  person President James K. Polk    schedule 22.08.2019
comment
@MaartenBodewes: вы уже отвечали на этот вопрос ранее.   -  person President James K. Polk    schedule 22.08.2019
comment
Ах да, с 2844 другими ответами начинаешь забывать :)   -  person Maarten Bodewes    schedule 22.08.2019


Ответы (2)


Я полагаю, вы ищете произвольный доступ к зашифрованным данным ctr< /а>; Метод Skip в CipherInputStream просто не делает этого и является «независимым от версии 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; он пропускает только то, что доступно в обуфере (просто увеличивает 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");
}
person kamyar haqqani    schedule 21.08.2019
comment
Это определенно неправильно. Вызов skip для базового потока ввода продвигает поток без продвижения счетчика. Данные, которые затем расшифровываются, не будут правильными. - person President James K. Polk; 22.08.2019
comment
@ Джеймс К. Полк, ты прав; оно поскользнулось; плохо исправить это; - person kamyar haqqani; 22.08.2019
comment
Все еще немного опасно, я бы не вызывал пропуск до тех пор, пока cc не станет равным нулю, я бы предпочел считать байты, которые необходимо пропустить, а не полагаться на значение 0 (что может произойти, например, из-за того, что файл короче). Или, как указано, лучше вообще на него не полагаться. - person Maarten Bodewes; 22.08.2019

После исследования я пришел к выводу, что вам следует реализовать 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();}
person Ali Elahi    schedule 01.09.2019