0

When i use this code on android Marshmallow(Android 6.0.1) ,the decryption is Ok but when i run on device with android Oreo(Android 8) the decryption value is not the same and data is not Correct.

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");
}

buffer data is ok in android 6 but in android 8 the data is not correct.

President James K. Polk
  • 40,516
  • 21
  • 95
  • 125
Ali Elahi
  • 131
  • 1
  • 10
  • Welcome to SO. Please to get better answer post-minimal, complete, verifiable with example. – kelalaka Aug 21 '19 at 11:59
  • 2
    Could you test the values the `skip` method returns? That's always a dangerous method, and the API leaves the possibility to skip fewer bytes than requested. It is also unclear what it does with the underlying cipher: are the bytes passed through it or not? Possibly you should skip the parent stream instead. – Maarten Bodewes Aug 21 '19 at 13:07
  • 2
    @MaartenBodewes: The `skip()` method works and passes data through the cipher, but you must absolutely check the return value. The OpenJDK version will never skip more than 512 bytes per call due the size of an internal buffer. I don't know what Android does but it is probably similar. – President James K. Polk Aug 21 '19 at 17:06
  • 2
    Personally I would solve this by using memory mapping the file rather than using streams. And you can set the counter in CTR mode by smartly altering the IV, there is no need to skip anything in the cipher itself. – Maarten Bodewes Aug 21 '19 at 22:21
  • I should point out that `skip()` works in the sense that it properly skips plaintext, but it is completely useless for your problem. Either use `read()` and discard bytes or, much more efficient, smartly alter the IV per @MaartenBodewes suggestion and `seek()` to the offset in the file you are interested, on a block (16 bytes) boundary. If the stream is not seekable then you are stuck using `read()` – President James K. Polk Aug 22 '19 at 01:48
  • @MaartenBodewes: you've already answered this question [before](https://stackoverflow.com/a/23744623/238704). – President James K. Polk Aug 22 '19 at 01:53
  • Oh, yeah, with 2844 other answers you start to forget :) – Maarten Bodewes Aug 22 '19 at 09:37

2 Answers2

1

I believe you're looking for random access to ctr encrypted data; Skip method in CipherInputStream just doesn't do that and is 'Android version independent'(still in use; not deprecated or replaced since api level 1!);

Take a look at CipherInputStream class file; It has few internal properties:

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;

and this is what skip does in 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;
}

It doesn't load new data to obuffer or ibuffer; it only skips on what is available in the obuffer( just increases ostart);

This should do it(has room for improvement):

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");
}
kamyar haqqani
  • 748
  • 6
  • 19
  • This is definitely wrong. Calling skip on the underlying inputstream advances the stream without advancing the counter. The data then being decrypted will not be correct. – President James K. Polk Aug 22 '19 at 01:41
  • @James K Polk you're right; it slipped; ill fix it; – kamyar haqqani Aug 22 '19 at 02:53
  • 1
    Still slightly dangerous, I would not call skip until `cc` is zero, I'd rather count down the bytes that need to be skipped rather than relying on the 0 value (which could happen because the file is shorter, for instance). Or, as indicated, it's better not to rely on it at all. – Maarten Bodewes Aug 22 '19 at 09:44
1

I came to the conclusion after research .you should implement InputStream with specific Cipher.

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();}
Ali Elahi
  • 131
  • 1
  • 10