【问题标题】:Is it possible to generate a 64-byte (256-bit) key and store/retrieve it with AndroidKeyStore?是否可以生成 64 字节(256 位)密钥并使用 AndroidKeyStore 存储/检索它?
【发布时间】:2019-01-11 14:12:33
【问题描述】:

在我的 Android 应用中,我需要一种方法来加密我存储在本地数据库中的数据。 我选择 Realm DB 是因为它提供了与加密的无缝集成。我只需要在初始化 Realm 实例时传递一个密钥。此密钥的大小必须为 64 字节。

出于安全原因,我发现存储此密钥的最佳方式是在 AndroidKeyStore 中。我正在努力寻找一种方法来生成具有该大小的密钥(使用任何算法),并将其放入 64 字节数组中。我正在尝试保留 API 19 的 minSdk,但我相信如果需要我可以将它提高到 23(这两个版本之间对 AndroidKeyStore 进行了许多更改)。

有人有想法吗?这是我的代码:

类加密.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));
    }
}

【问题讨论】:

  • 64 字节 = 512 位。

标签: java android encryption android-keystore


【解决方案1】:

AndroidKeyStore 的意义在于将敏感的密钥材料从您的应用中移出,移出操作系统并转移到安全的硬件中,使其永远不会泄漏或受到损害。因此,按照设计,如果您在 AndroidKeyStore 中创建密钥,则永远无法获取密钥材料。

在这种情况下,Realm DB 需要密钥材料,所以你不能给它一个 AndroidKeyStore 密钥。此外,Realm 想要的是两个 AES 密钥,而不是您尝试生成的 EC 密钥。

生成你需要的密钥材料的正确方法是:

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.

只有 64 个随机字节。但是,您将需要将这些字节存储在某处。您可以使用 AndroidKeyStore 创建一个 AES 密钥并使用它来加密 dbKey。比如:

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

您需要将ivencryptedDbKey 保存在某个地方(不是在数据库中!),以便您可以恢复dbKey。然后你可以解密它:

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.

但是,尽管如此……我认为您不应该这样做。我认为这实际上并没有给你任何Android默认情况下没有给你的安全性。如果攻击者试图转储包含您的数据库的设备存储,他将一无所获,因为 Android 无论如何都会加密所有存储。如果攻击者可以 root 设备,他可以将代码作为您的应用程序运行并使用它来解密 dbKey,就像您的应用程序一样。

如果您在dbKeyWrappingKey 上添加一些额外的保护,AndroidKeyStore 可能真正增加价值的地方。例如,如果您将其设置为要求用户在五分钟内进行身份验证,则只有当用户在附近输入他们的 PIN/图案/密码或触摸指纹扫描仪时,才能使用dbWrappingKey 解密dbKey .请注意,这仅在用户有 PIN/模式/密码时才有效,但如果他们没有,那么您的数据库对任何拿起电话的人都是开放的。

请参阅KeyGenParameterSpec,了解您可以采取哪些措施来限制dbKeyWrappingKey 的使用方式。

【讨论】:

    【解决方案2】:

    据我所知,解决这个问题的常用方法是,你自己生成你需要大小的随机密钥(主密钥),这个主密钥可以在密钥的帮助下加密商店。

    1. 生成您自己的所需大小的随机主密钥。
    2. 使用此主密钥加密数据(例如对称加密)。
    3. 借助密钥库加密主密钥。
    4. 将加密的主密钥存储在某处。

    解密您的数据:

    1. 读取加密的主密钥。
    2. 借助密钥库解密主密钥。
    3. 使用主密钥解密数据。

    换句话说,存储在密钥库中的不是主密钥,而是密钥库可用于保护/加密您的主密钥。

    【讨论】:

    • 感谢您的回复。我似乎与您所说的使用 Cypher 的实现类似。看来这是要走的路,不幸的是在我的情况下。
    猜你喜欢
    • 1970-01-01
    • 2013-06-16
    • 2015-04-28
    • 1970-01-01
    • 1970-01-01
    • 2013-10-19
    • 1970-01-01
    • 2016-02-12
    • 1970-01-01
    相关资源
    最近更新 更多