简体   繁体   English

是否可以生成64字节(256位)密钥并使用AndroidKeyStore存储/检索它?

[英]Is it possible to generate a 64-byte (256-bit) key and store/retrieve it with AndroidKeyStore?

In my Android app, I need a way to encrypt the data I store in a local DB. 在我的Android应用程序中,我需要一种对存储在本地数据库中的数据进行加密的方法。 I chose Realm DB because the offer a seamless integration with encryption. 我选择Realm DB是因为它提供了与加密的无缝集成。 I just need to pass a key when initializing the Realm instance. 我只需要在初始化Realm实例时传递密钥。 This key must be of 64 byte size. 该密钥必须为64字节大小。

For security reason, I found out that the best way to store this key is in AndroidKeyStore. 出于安全原因,我发现最好的存储方式是在AndroidKeyStore中。 I'm struggling to find a way to generate a key (using any algorithm) with that size, and getting it into a 64-byte array. 我正在努力寻找一种方法来生成具有该大小的密钥(使用任何算法),并将其放入64字节数组中。 I'm trying to keep a minSdk of API 19, but I believe I can bump it up to 23 if needed (many changes to AndroidKeyStore between these two versions). 我正在尝试保留API 19的minSdk,但我相信如果需要可以将其提高到23(这两个版本之间对AndroidKeyStore进行了许多更改)。

Does anyone have an idea? 有人有主意吗? Here is my code: 这是我的代码:

Class Encryption.java 类Encryption.java

private static KeyStore ks = null;
private static String ALIAS = "com.oi.pap";

public static byte[] loadkey(Context context) {

    byte[] content = new byte[64];
    try {
        if (ks == null) {
            createNewKeys(context);
        }

        ks = KeyStore.getInstance("AndroidKeyStore");
        ks.load(null);

        content= ks.getCertificate(ALIAS).getEncoded(); //<----- HERE, I GET SIZE GREATER THAN 64
        Log.e(TAG, "original key :" + Arrays.toString(content));
    } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    content = Arrays.copyOfRange(content, 0, 64); //<---- I would like to remove this part.
    return content;
}

private static void createNewKeys(Context context) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {

    ks = KeyStore.getInstance("AndroidKeyStore");
    ks.load(null);
    try {
        // Create new key if needed
        if (!ks.containsAlias(ALIAS)) {
            Calendar start = Calendar.getInstance();
            Calendar end = Calendar.getInstance();
            end.add(Calendar.YEAR, 1);
            KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
                    .setAlias(ALIAS)
                    .setSubject(new X500Principal("CN=PapRealmKey, O=oipap"))
                    .setSerialNumber(BigInteger.ONE)
                    .setStartDate(start.getTime())
                    .setEndDate(end.getTime())
                    .setKeySize(256)
                    .setKeyType(KeyProperties.KEY_ALGORITHM_EC)
                    .build();
            KeyPairGenerator generator = KeyPairGenerator
                    .getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
            generator.initialize(spec);

            KeyPair keyPair = generator.generateKeyPair();
            Log.e(TAG, "generated key :" + Arrays.toString(keyPair.getPrivate().getEncoded()));

        }
    } catch (Exception e) {
        Log.e(TAG, Log.getStackTraceString(e));
    }
}

The point of AndroidKeyStore is to move sensitive key material out of your app, out of the operating system and into secure hardware where it can never leak or be compromised. AndroidKeyStore的目的是将敏感密钥材料从您的应用程序,操作系统中移出,并移入安全的硬件中,绝不会泄漏或受到损害。 So, by design, if you create a key in AndroidKeyStore, you can never get the key material out. 因此,根据设计,如果您在AndroidKeyStore中创建密钥,则永远无法获取密钥资料。

In this case, Realm DB wants the secret key material, so you can't give it an AndroidKeyStore key. 在这种情况下,Realm DB需要密钥材料,因此您不能为其提供AndroidKeyStore密钥。 Also, what Realm wants is two AES keys, not an EC key, as you were trying to generate. 另外,Realm想要的是您尝试生成的两个AES密钥,而不是EC密钥。

The right way to generate the key material you need is: 生成所需关键材料的正确方法是:

byte[] dbKey = new byte[64];
Random random = new SecureRandom();
random.nextBytes(dbKey);
// Pass dbKey to Realm DB...
Arrays.fill(dbKey, 0); // Wipe key after use.

Just 64 random bytes. 只有64个随机字节。 However, you're going to need to store those bytes somewhere. 但是,您将需要将这些字节存储在某个地方。 You could create an AES key with AndroidKeyStore and use it to encrypt dbKey . 您可以使用AndroidKeyStore创建AES密钥,并使用它来加密dbKey Something like: 就像是:

KeyGenerator keyGenerator = KeyGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(
        new KeyGenParameterSpec.Builder("dbKeyWrappingKey",
                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)      
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                .build());
SecretKey key = keyGenerator.generateKey();

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = cipher.getIV();
byte[] encryptedDbKey = cipher.doFinal(dbKey);

You'll need to save both iv and encryptedDbKey somewhere (not in the database!) so that you can recover dbKey . 你需要节省ivencryptedDbKey的地方(而不是在数据库!),这样就可以恢复dbKey Then you can decrypt it with: 然后您可以使用以下方法解密:

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
key = (SecretKey) keyStore.getKey("dbKeyWrappingKey", null);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
byte[] dbKey = cipher.doFinal(encryptedDbKey);
// Pass dbKey to Realm DB and then wipe it.

However, with all of that said... I don't think you should do any of it. 但是,尽管如此,我不认为您应该做任何事情。 I don't think this actually gives you any security that Android doesn't give you by default anyway. 我认为这实际上不会为您提供任何安全性,而Android默认情况下不会为您提供任何安全性。 If an attacker tries to dump the device storage, which contains your database, he'll get nothing because Android encrypts all of the storage anyway. 如果攻击者试图转储包含您的数据库的设备存储,则他将一无所获,因为Android仍然会加密所有存储。 If an attacker can root the device, he can run code as your app and use it to decrypt dbKey the same way your app does. 如果攻击者可以启动设备,则他可以像您的应用程序一样运行代码,并以与您的应用程序相同的方式使用它来解密dbKey

Where AndroidKeyStore may really add value is if you add some additional protections on dbKeyWrappingKey . 如果您在dbKeyWrappingKey上添加一些其他保护,则AndroidKeyStore可能真正增加价值的dbKeyWrappingKey For example, if you set it to require user authentication within, say five minutes, it will only be possible to use dbWrappingKey to decrypt dbKey when the user is around to enter their PIN/pattern/password or touch the fingerprint scanner. 例如,如果将其设置为要求在五分钟之内进行用户身份验证,则只有当用户在附近输入PIN /图案/密码或触摸指纹扫描器时,才可以使用dbWrappingKey解密dbKey Note that this only works if the user has a PIN/pattern/password, but if they don't, well, your database is wide open to anyone who picks up the phone anyway. 请注意,这仅在用户具有PIN /图案/密码的情况下才有效,但是如果没有,则您的数据库对任何仍然接听电话的人都是开放的。

See KeyGenParameterSpec for all of the things you can do to restrict the ways dbKeyWrappingKey can be used. 有关限制dbKeyWrappingKey使用方式的所有操作,请参见KeyGenParameterSpec

As far as I know, the usual way to solve this problem is, that you generate your own random key of the size you need (master-key), and this master-key can be encrypted with the help of the key store. 据我所知,解决此问题的通常方法是,生成自己所需大小的随机密钥(主密钥),并且可以在密钥存储的帮助下对该主密钥进行加密。

  1. Generate your own random master-key of the size you need. 生成自己所需大小的随机主密钥。
  2. Encrypt data with this master-key (eg symmetric encryption). 使用此主密钥加密数据(例如,对称加密)。
  3. Encrypt the master-key with the help of the key-store. 在密钥存储的帮助下加密主密钥。
  4. Store the encrypted master-key somewhere. 将加密的主密钥存储在某处。

To decrypt your data: 解密数据:

  1. Read the encrypted master-key. 读取加密的主密钥。
  2. Decrypt the master-key with the help of the key-store. 在密钥库的帮助下解密主密钥。
  3. Decrypt data with the master-key. 用主密钥解密数据。

In other words, it is not the master-key which is stored inside the key-store, but the key-store can be used to protect/encrypt your master-key. 换句话说,不是主密钥存储在密钥存储区中,但是密钥存储区可用于保护/加密您的主密钥。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM