使用AndroidKeyStore进行AES加密

4
Android M提供了通过AndroidKeyStore进行AES支持,但是我无法找到任何生成密钥的组合,可以提供完全加密和解密的方法,而不需要用户密码/设备锁定。看起来我的当前方法对于这些要求是适当的,因为密钥库正在存储我的密钥,我可以加载密钥并执行加密,在加密过程中保留IV的情况下,我可以解密数据。

不幸的是,在实际的使用情况中,我不能保留IV以便稍后解密而不将其写入磁盘,也许这就是我应该做的?

我已经浏览了SDK中更新的密钥库和相关测试,但是无法找到任何可用作示例的测试用例。示例似乎也缺少实际使用由AndroidKeyStore生成的SecretKeys而不将它们绑定到设备锁定/指纹。

我创建了一个存储库,以尝试突出显示我所做的事情,并包括一些解释我的问题所在的评论。相关代码也包括在内。

为了清晰起见,我的问题只是如何生成一个AndroidKeyStore支持的AES SecretKey,允许我通过密码输入/输出流进行加密和解密,而不必将IV写入磁盘或使用指纹/设备锁定方法?

https://github.com/ToxicBakery/AES-Testing/blob/master/app/src/androidTest/java/com/toxicbakery/app/aes/AesTest.java

    final String suchAlphabet = "abcdefghijklmnopqrstuvwxyz";

    KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
    keyStore.load(null);

    /*
    KEY GENERATION
     */

    // Define the key spec
    KeyGenParameterSpec aesSpec = new KeyGenParameterSpec.Builder(ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
            .setKeySize(128)
            .build();

    // Create the secret key in the key store
    KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
    keyGenerator.init(aesSpec);
    keyGenerator.generateKey();

    Cipher cipher;
    SecretKey secretKey;

    /*
    ENCRYPTION
     */

    // Load the secret key and encrypt
    secretKey = ((KeyStore.SecretKeyEntry) keyStore.getEntry(ALIAS, null)).getSecretKey();
    cipher = Cipher.getInstance(TRANSFORMATION);
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);

    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
    cipherOutputStream.write(suchAlphabet.getBytes());
    cipherOutputStream.flush();
    cipherOutputStream.close();

    /*
    DECRYPTION
     */

    // Load the secret key and decrypt
    secretKey = ((KeyStore.SecretKeyEntry) keyStore.getEntry(ALIAS, null)).getSecretKey();

    // The following two lines attempt to represent real world usage in that the previous line loaded
    // the key from the store and the next two lines attempt to create the cipher and then initialize
    // the cipher such that an IV can be extracted as it does not seem that you can use the spec or the
    // parameters. Interestingly, the following two lines only 'half' such that a-p fail to decrypt and
    // q-z decrypt successfully 100% of the time. Leaving the lines commented results an in a successful
    // decryption of the alphabet but this is not a usable scenario
    //
    //        cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
    //        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

    IvParameterSpec ivParameterSpec = new IvParameterSpec(cipher.getIV());
    cipher = Cipher.getInstance(TRANSFORMATION);
    cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);

    byte[] in = new byte[suchAlphabet.getBytes().length];
    ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
    CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
    IOUtils.readFully(cipherInputStream, in);
    cipherInputStream.close();

    /*
    VERIFY
     */

    String muchWow = new String(in);
    assertEquals(suchAlphabet, muchWow);
2个回答

3

Alex的上面的回答是最好的,但请注意还有另一个选择:自己将IV设置为固定值或者能够一致派生的值。使用相同IV对给定数据进行多次加密是不安全的,因此AndroidKeyStore不鼓励这样做。但如果您确信要这样做,可以使用setRandomizedEncryptionRequired方法,如下所示:

KeyGenParameterSpec aesSpec = new KeyGenParameterSpec.Builder(ALIAS, //...
    // ...
    .setRandomizedEncryptionRequired(false)
    .build()

如果需要提供IV,那么在Cipher初始化调用中,您可以添加第三个参数:IvParameterSpec对象。例如:

cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(myIv));

对于解密,使用相同的IV值,它将正确解密。

再次强调,这种方法不被推荐使用。除非您确切地理解为什么这是一个坏主意并且有非常具体的原因知道在您的情况下它没问题,否则最好让密钥库生成随机的IV。通常可以很容易地找到一个存储带有密文的IV的位置。


1
两个答案都很好,但这个解释得更清楚。另外,阅读Android文档中的KeyGenParameterSpec.Builder.setRandomizedEncryptionRequired会有所帮助,这将进一步解释。现在我知道IV的用途了。 - Pointer Null

2

将IV保存在密文旁边,然后稍后使用IV解密密文。但在您的实际场景中,为什么不能持久保存IV呢?


忘了更新一下,但是确实没有意识到在配置中需要存储IV。我的问题更多的是你要用IV做什么,我想出了一个与密钥存储绑定的RSA来存储它的系统,并且提出了向18版本兼容的实现,所以一切都很好。 - ian.shaun.thomas
在安卓系统中,我应该把IV存储在哪里?SharedPreference在已经获取root权限的设备上很容易被破解。 - Bytecode

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接