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

Java 是否可以生成64字节(256位)的密钥并使用AndroidKeyStore存储/检索它?,java,android,encryption,android-keystore,Java,Android,Encryption,Android Keystore,在我的Android应用程序中,我需要一种方法来加密存储在本地数据库中的数据。 我选择RealmDB是因为它提供了与加密的无缝集成。我只需要在初始化领域实例时传递一个密钥。此密钥的大小必须为64字节 出于安全原因,我发现存储此密钥的最佳方法是在AndroidKeyStore中。我正在努力寻找一种方法来生成一个具有该大小的密钥(使用任何算法),并将其放入一个64字节的数组中。我试图保留API 19的minSdk,但我相信如果需要,我可以将其增加到23(这两个版本之间对AndroidKeyStore

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

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

有人有主意吗?这是我的密码:

类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));
    }
}
私有静态密钥库ks=null;
私有静态字符串ALIAS=“com.oi.pap”;
公共静态字节[]加载键(上下文){
字节[]内容=新字节[64];
试一试{
如果(ks==null){
createNewKeys(上下文);
}
ks=KeyStore.getInstance(“AndroidKeyStore”);
ks.load(空);

content=ks.getCertificate(ALIAS.getEncoded();//据我所知,解决此问题的通常方法是,生成自己所需大小的随机密钥(主密钥),并且可以在密钥存储的帮助下对该主密钥进行加密

  • 生成自己需要的大小的随机主密钥
  • 使用此主密钥加密数据(例如对称加密)
  • 在密钥存储的帮助下加密主密钥
  • 将加密的主密钥存储在某处
  • 要解密数据,请执行以下操作:

  • 读取加密的主密钥
  • 在密钥存储的帮助下解密主密钥
  • 使用主密钥解密数据

  • 换句话说,存储在密钥存储中的不是主密钥,而是密钥存储可以用来保护/加密主密钥。

    据我所知,解决此问题的通常方法是,生成自己需要的随机密钥(主密钥),此主密钥可以在密钥存储的帮助下进行加密

  • 生成自己需要的大小的随机主密钥
  • 使用此主密钥加密数据(例如对称加密)
  • 在密钥存储的帮助下加密主密钥
  • 将加密的主密钥存储在某处
  • 要解密数据,请执行以下操作:

  • 读取加密的主密钥
  • 在密钥存储的帮助下解密主密钥
  • 使用主密钥解密数据

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

    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);
    
    您需要将
    iv
    encryptedbkey
    保存在某个位置(不在数据库中!),以便恢复
    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.
    
    然而,尽管如此……我认为你不应该这样做。我不认为这实际上给了你安卓默认情况下没有给你的任何安全性。如果攻击者试图转储包含你数据库的设备存储,他将一无所获,因为安卓加密了所有存储。如果攻击者可以root在这个设备上,他可以像你的应用程序一样运行代码,并用它来解密
    dbKey
    ,就像你的应用程序那样

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


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

    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);
    
    您需要将
    iv
    encryptedbkey
    保存在某个位置(不在数据库中!),以便恢复
    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.
    
    然而,尽管如此……我认为你不应该这样做。我不认为这实际上给了你安卓默认情况下没有给你的任何安全性。如果攻击者试图转储设备存储,哪一个