代码之家  ›  专栏  ›  技术社区  ›  lucas.mdo

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

  •  0
  • lucas.mdo  · 技术社区  · 6 年前

    在我的Android应用程序中,我需要一种方法来加密我存储在本地数据库中的数据。 我选择领域数据库是因为它提供了与加密的无缝集成。初始化领域实例时只需要传递一个密钥。此密钥的大小必须为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));
        }
    }
    
    2 回复  |  直到 6 年前
        1
  •  1
  •   divegeek    6 年前

    androidkeystore的要点是将敏感的密钥材料从应用程序、操作系统和安全硬件中移出,使其永远不会泄漏或受到危害。因此,根据设计,如果您在androidkeystore中创建了一个密钥,就永远无法取出密钥材料。

    在这种情况下,realm db需要密钥材料,所以不能给它androidkeystore密钥。另外,领域需要的是两个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 encryptedDbKey 某处(不在数据库中!)这样你就可以恢复 数据库密钥 . 然后您可以用以下方法解密:

    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会加密所有存储。如果攻击者可以将设备根目录,他可以运行代码作为您的应用程序并使用它进行解密。 数据库密钥 和你的应用程序一样。

    如果您在 dbKeyWrappingKey . 例如,如果将其设置为要求在五分钟内(例如五分钟内)进行用户身份验证,则只能使用 dbWrappingKey 解密 数据库密钥 当用户在附近输入其PIN/图案/密码或触摸指纹扫描仪时。请注意,这只在用户有一个密码/模式/密码的情况下有效,但如果没有,那么,您的数据库对任何拿起电话的人都是开放的。

    KeyGenParameterSpec 为了所有你能做的事情来限制 dbkeywrapping键 可以使用。

        2
  •  0
  •   martinstoeckli    6 年前

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

    1. 生成您所需大小的随机主密钥。
    2. 使用此主密钥加密数据(例如对称加密)。
    3. 在密钥存储的帮助下加密主密钥。
    4. 将加密的主密钥存储在某个地方。

    要解密数据:

    1. 读取加密的主密钥。
    2. 在密钥存储的帮助下解密主密钥。
    3. 用主密钥解密数据。

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