存储 RSA 私钥 Android

Posted

技术标签:

【中文标题】存储 RSA 私钥 Android【英文标题】:Storing RSA Private Key Android 【发布时间】:2013-12-04 20:05:15 【问题描述】:

在创建用于加密/解密消息并通过互联网发送消息的简单消息传递 android 应用程序期间,我决定使用 RSA 公钥/私钥加密。问题是如何存储私钥,这样即使手机被恶意root,密钥也能保持安全?据我了解, KeyStore 用于证书,不能用于此?我应该使用 AES 将私钥加密为文本文件吗?我在安全方面的经验很少,所以请随时纠正我的想法,并提出您的意见!

亲切的问候。

【问题讨论】:

@LokiSinclair 您可以将公钥放在任何您喜欢的地方,并展示给您喜欢的任何人,这就是它公开的原因。所以用私钥存储它是好的。您只需要将私钥保存在安全的地方。 @LokiSinclair 抱歉,这是错误的。私钥最好存储在应该能够解密消息的设备上。可以在线进行备份,但这会带来安全风险。此外,将公钥与私钥存储在同一位置没有任何风险。 【参考方案1】:

我认为 KeyStore 可能适合您使用。它能够存储 RSA 密钥并使用 AES 对其进行加密,因此即使具有 root 访问权限,也无法在没有密码或暴力破解的情况下提取它们。

这里有一篇关于使用 KeyStore 的好帖子:http://nelenkov.blogspot.fr/2012/05/storing-application-secrets-in-androids.html

【讨论】:

感谢您的回复,您链接的博客非常有用!有一篇关于基于密码的加密的帖子,我发现它比依赖 KeyStore 更合适和更可取,至少对于我的应用程序而言。再次感谢您!【参考方案2】:

您可以在 Android 上使用 SharedPreference 来持久化您的 RSA 公钥/私钥。 为了在手机被恶意root时确保您的密钥安全,您可以执行以下步骤:

1:当您想加密任何数据时,生成一个密钥对。 2:提示用户输入密码。 3:使用该密码生成对称密钥来加密您的私钥。 4:您可以使用公钥加密您的数据并使用私钥解密。 5:您可以为步骤2中提示的密码保留一个会话。在该会话期间,您可以使用对称密钥(由密码生成)来加密/解密私钥。

以下代码 sn-p 展示了如何存储和获取公钥

public void setPublicKey(PublicKey publicKey, String key, Context context) 

    byte[] pubKey = publicKey.getEncoded();
    String pubKeyString = Base64.encodeBytes(pubKey);
    this.setString(key, pubKeyString, context);


public PublicKey getPublicKey(String key,Context context) 

    PublicKey pKey = null;
    try 

        String pubString = this.getString(key, context);

        if(pubString!=null) 
            byte[] binCpk = Base64.decode(pubString);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(binCpk);
            pKey = keyFactory.generatePublic(publicKeySpec);
        
        catch(Exception e)
    
    return pKey;

以下代码 sn-p 展示了如何存储和获取私钥。

public void setPrivateKey(PrivateKey privateKey, String key, Context context) 

    byte[] priKey = privateKey.getEncoded();
    String priKeyString = Base64.encodeBytes(priKey);
    this.setString(key, priKeyString, context);


public PrivateKey getPrivateKey(String key, Context context) 

    PrivateKey privateKey = null;

    try 
        String privateString = this.getString(key, context);
        if(privateString!=null)
            byte[] binCpk = Base64.decode(privateString);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(binCpk);
            privateKey = keyFactory.generatePrivate(privateKeySpec);
        
     
    catch(Exception e)
    
    return privateKey;

【讨论】:

请不要对私钥这样做。正确比看起来更难。使用内置的密钥库 API(在 Android 4.3+ 中可用)或使用常规的 KeyStore (BKS/JKS) 来存储您的密钥和证书。该格式具有完整性保护和加密功能,可以通过密码进行保护。 当我说你只是在 set 方法中编码密钥时我说得对吗?【参考方案3】:

文件系统中的任何密钥库(P12、JKS、AKS)都不能足够安全地保存 RSA 私钥。只有智能卡或安全令牌可以提供高级别的安全性。阅读本书:“Android Security Internals”。在本书中,您将找到对 Android 安全和 JCA 提供程序的详细描述。

【讨论】:

有趣的是,“Android Security Internals”的作者本人建议使用“常规KeyStore (BKS/JKS) 来存储您的密钥和证书”。只需阅读先前答案中的 cmets 即可。建议仍然有效。 Nikolay Elenkov 的好书和博客。【参考方案4】:

是的,您可以使用 KeyStore 将您的 RSA PrivateKey 保存在 Android Studio 中,并根据需要检索它以进行签名。基本思想是在生成密钥时使用“AndroidKeystore”作为提供者。这个人:https://***.com/questions/49410575/keystore-operation-failed-with-rsa-sign-and-verify#= 的重点是确保设置签名填充。这让它对我有用,如下所示:

public void storeKeyAsymmetric()    //Generate the keys (public and private together) using KeyStore
KeyPairGenerator kpGenerator = null;
    try 
        kpGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
     catch (NoSuchAlgorithmException e) 
        e.printStackTrace();
    
    catch (NoSuchProviderException e) 
        e.printStackTrace();
    
    try 
        kpGenerator.initialize(new KeyGenParameterSpec.Builder("aliasOfYourChoice", KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
                .setDigests(KeyProperties.DIGEST_SHA512, KeyProperties.DIGEST_SHA256)
                .setKeySize(2048)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, KeyProperties.ENCRYPTION_PADDING_RSA_OAEP, KeyProperties.ENCRYPTION_PADDING_NONE)
                .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, KeyProperties.SIGNATURE_PADDING_RSA_PSS)
                .build());
        keyPairAsymmetric = kpGenerator.generateKeyPair();
        devicePublic = keyPairAsymmetric.getPublic();
        byte[] encoding = devicePublic.getEncoded();
        strDevicePublicPEM = Crypto.writePEM(encoding);
     catch (InvalidAlgorithmParameterException e) 
        e.printStackTrace();
    

稍后,您可以使用该私钥对消息进行签名,如下所示:

public static String verifiedDeviceSignature(String dataToSign)
    boolean verified = false;
    String signature = null;
    MessageDigest digest = null;
    try 
        digest = MessageDigest.getInstance("SHA-512");
     catch (NoSuchAlgorithmException e) 
        e.printStackTrace();
    
    digest.update(dataToSign.getBytes(StandardCharsets.UTF_8));
    byte[] hash = digest.digest();

    try 
        KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
        ks.load(null);
        //******This is a PrivateKeyEntry
        KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry("aliasOfYourChoice", null);  //null if you don't have key locked up with password
        PrivateKey privateKey = privateKeyEntry.getPrivateKey();
        Signature s = Signature.getInstance("SHA512withRSA");
        s.initSign(privateKey);
        s.update(dataToSign.getBytes(StandardCharsets.UTF_8));  //TODO:  Change this to hash
        byte[] sig = s.sign();

        PublicKey publicKey = ks.getCertificate("aliasOfYourChoice").getPublicKey();

        Signature v = Signature.getInstance("SHA512withRSA");
        v.initVerify(publicKey);
        v.update(dataToSign.getBytes(StandardCharsets.UTF_8));  //TODO:  Change this to hash
        verified = v.verify(sig);
        String strSig = new String(Base64.encode(sig, 2));
        signature = strSig;
     catch (KeyStoreException e) 
        e.printStackTrace();
     catch (CertificateException e) 
        e.printStackTrace();
     catch (IOException e) 
        e.printStackTrace();
     catch (NoSuchAlgorithmException e) 
        e.printStackTrace();
     catch (UnrecoverableEntryException e) 
        e.printStackTrace();
     catch (InvalidKeyException e) 
        e.printStackTrace();
     catch (SignatureException e) 
        e.printStackTrace();
    

    if(verified)
        Log.d("***verifiedDeviceSignature*: ", "Signature Verified");
        //TODO:  URL encode
        return signature;
    else 
        return "Not verified.";
    

【讨论】:

以上是关于存储 RSA 私钥 Android的主要内容,如果未能解决你的问题,请参考以下文章

WinRT 存储 RSA 私钥的位置

如何通过 RSA 生成唯一的公钥和私钥

java rsa加密,高并发如何解决

PHP:从私钥字符串/文件中获取 RSA 公钥

RSA 私钥的 PKCS#1 和 PKCS#8 格式 [关闭]

RSA Crypto:第一次生成后是不是需要存储密钥对