当前位置: 首页 > 知识库问答 >
问题:

我如何使用Android KeyStore安全地存储任意字符串?

伍胡媚
2023-03-14
String metaKey = "ourSecretKey";
String encodedKey = "this is supposed to be a secret";
byte[] encodedKeyBytes = new byte[(int)encodedKey.length()];
encodedKeyBytes = encodedKey.getBytes("UTF-8");
KeyStoreParameter ksp = null;

//String algorithm = "DES";
String algorithm = "DESede";
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
SecretKeySpec secretKeySpec = new SecretKeySpec(encodedKeyBytes, algorithm);
SecretKey secretKey = secretKeyFactory.generateSecret(secretKeySpec);

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());

keyStore.load(null);

KeyStore.SecretKeyEntry secretKeyEntry = new KeyStore.SecretKeyEntry(secretKey);
keyStore.setEntry(metaKey, secretKeyEntry, ksp);

keyStore.store(null);

String recoveredSecret = "";
if (keyStore.containsAlias(metaKey)) {
    KeyStore.SecretKeyEntry recoveredEntry = (KeyStore.SecretKeyEntry)keyStore.getEntry(metaKey, ksp);
    byte[] bytes = recoveredEntry.getSecretKey().getEncoded();
    for (byte b : bytes) {
        recoveredSecret += (char)b;
     }
}
Log.v(TAG, "recovered " + recoveredSecret);

共有1个答案

邢寒
2023-03-14

我修改了Patrick Brennan的公认答案。在Android9上,它产生了一个NosuchAlgorithmException。已将不推荐使用的KeyPairGeneratorSpec替换为KeyPairGenerator。还需要进行一些工作来解决有关填充的例外情况。

代码被注释为所做的更改:“***”

@RequiresApi(api = Build.VERSION_CODES.M)
public static void storeExistingKey(Context context) {

    final String TAG = "KEY-UTIL";

    try {
        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);

        String alias = "key11";
        int nBefore = keyStore.size();

        // Create the keys if necessary
        if (!keyStore.containsAlias(alias)) {

            Calendar notBefore = Calendar.getInstance();
            Calendar notAfter = Calendar.getInstance();
            notAfter.add(Calendar.YEAR, 1);


            // *** Replaced deprecated KeyPairGeneratorSpec with KeyPairGenerator
            KeyPairGenerator spec = KeyPairGenerator.getInstance(
                    // *** Specified algorithm here
                    // *** Specified: Purpose of key here
                    KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
            spec.initialize(new KeyGenParameterSpec.Builder(
                    alias, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT) 
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) //  RSA/ECB/PKCS1Padding
                    .setKeySize(2048)
                    // *** Replaced: setStartDate
                    .setKeyValidityStart(notBefore.getTime())
                    // *** Replaced: setEndDate
                    .setKeyValidityEnd(notAfter.getTime())
                    // *** Replaced: setSubject
                    .setCertificateSubject(new X500Principal("CN=test"))
                    // *** Replaced: setSerialNumber
                    .setCertificateSerialNumber(BigInteger.ONE)
                    .build());
            KeyPair keyPair = spec.generateKeyPair();
            Log.i(TAG, keyPair.toString());
        }

        int nAfter = keyStore.size();
        Log.v(TAG, "Before = " + nBefore + " After = " + nAfter);

        // Retrieve the keys
        KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null);
        PrivateKey privateKey = privateKeyEntry.getPrivateKey();
        PublicKey publicKey = privateKeyEntry.getCertificate().getPublicKey();


        Log.v(TAG, "private key = " + privateKey.toString());
        Log.v(TAG, "public key = " + publicKey.toString());

        // Encrypt the text
        String plainText = "This text is supposed to be a secret!";
        String dataDirectory = context.getApplicationInfo().dataDir;
        String filesDirectory = context.getFilesDir().getAbsolutePath();
        String encryptedDataFilePath = filesDirectory + File.separator + "keep_yer_secrets_here";

        Log.v(TAG, "plainText = " + plainText);
        Log.v(TAG, "dataDirectory = " + dataDirectory);
        Log.v(TAG, "filesDirectory = " + filesDirectory);
        Log.v(TAG, "encryptedDataFilePath = " + encryptedDataFilePath);

        // *** Changed the padding type here and changed to AndroidKeyStoreBCWorkaround
        Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
        inCipher.init(Cipher.ENCRYPT_MODE, publicKey);

        // *** Changed the padding type here and changed to AndroidKeyStoreBCWorkaround
        Cipher outCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
        outCipher.init(Cipher.DECRYPT_MODE, privateKey);

        CipherOutputStream cipherOutputStream =
                new CipherOutputStream(
                        new FileOutputStream(encryptedDataFilePath), inCipher);
        // *** Replaced string literal with StandardCharsets.UTF_8
        cipherOutputStream.write(plainText.getBytes(StandardCharsets.UTF_8));
        cipherOutputStream.close();

        CipherInputStream cipherInputStream =
                new CipherInputStream(new FileInputStream(encryptedDataFilePath),
                        outCipher);
        byte[] roundTrippedBytes = new byte[1000]; // TODO: dynamically resize as we get more data

        int index = 0;
        int nextByte;
        while ((nextByte = cipherInputStream.read()) != -1) {
            roundTrippedBytes[index] = (byte) nextByte;
            index++;
        }

        // *** Replaced string literal with StandardCharsets.UTF_8
        String roundTrippedString = new String(roundTrippedBytes, 0, index, StandardCharsets.UTF_8);
        Log.v(TAG, "round tripped string = " + roundTrippedString);

    } catch (NoSuchAlgorithmException | UnsupportedOperationException | InvalidKeyException | NoSuchPaddingException | UnrecoverableEntryException | NoSuchProviderException | KeyStoreException | CertificateException | IOException e | InvalidAlgorithmParameterException e) {
        e.printStackTrace();
}

注意:“AndroidKeyStoreBCWorkaround”允许代码跨不同的API工作。

 类似资料:
  • 问题内容: 我有一个 特殊情况, 要求我根据用户提供的输入值生成SQL WHERE子句的一部分。我想防止任何形式的SQL Injection漏洞。我想出了以下代码: 问题: 看到任何问题吗? 您能提供更好的解决方案吗? 是否有现有的库可以帮助您解决此问题? 笔记: 这是SO和其他地方的常见问题,但是我看到的唯一答案是始终使用PreparedStatements。Fwiw,我正在使用JasperRe

  • 我是一名学习密码学的学生。在网上搜索之后,我仍然找不到问题的答案。我想知道如何为电子商务网站安全地存储会话ID。如果可能的话,怎么可能?请用外行的话解释一下。期待您的回答。 干杯

  • 问题内容: 我通过通过PHP回显将XML文档发送给AJAX调用来进行响应。为了形成这个XML文档,我遍历了数据库的记录。问题在于数据库中包含带有’<’符号的记录。因此,浏览器自然会在该特定位置引发错误。如何解决? 问题答案: 通过使用(或可能更适当地)使用库来构建XML文档(例如DOMDocument或XMLWriter)来转义这些字符。 另一种替代方法是使用CDATA节,但是您必须注意是否出现。

  • 问题内容: 我有一个Spring应用程序,该应用程序使用最初由Spring Roo创建的JPA( Hibernate )。我需要存储任意长度的字符串,因此,我用 @Lob 注释了该字段: 该应用程序可以在本地主机上正常运行,但是我已将其部署到外部服务器,并且出现了编码问题。因此,我想检查存储在PostgreSQL数据库中的数据是否正确。该应用程序自动创建/更新表。并且为该字段(消息)创建了一个类型

  • 我在工作场所使用Git,公司政策不允许我以不安全的方式存储密码。有没有比使用

  • 允许访问简单的加密和解密字符串,以便存储在本地机器上。 进程:主进程 此模块保护磁盘上存储的数据不被其他应用程序或拥有完全磁盘访问权的用户访问。 请注意,在Mac上,需要访问系统Keychain,这些调用可以阻止当前线程来收集用户输入。 如果密码管理工具可用,Linux 亦是如此。 方法 safeStorage 模块包含以下方法: safeStorage.isEncryptionAvailable