在 Android 上安全地生成和存储公钥/私钥

信息安全 加密 公钥基础设施 RSA 秘密分享
2021-08-31 13:02:25

我正在制作一个 Web 应用程序,用于分发使用其客户端的公钥加密的数据。目前,这适用于我自己分发的专用设备。在发货之前,我将公钥/私钥对刷入设备固件。

然而,由于这是 2017 年,我还想制作一个 android 应用程序,让任何 android 设备都像我销售的设备之一,因为这对客户来说更容易。我遇到的唯一瓶颈是密钥的分配。

我可以在第一次启动应用程序时在 Android 设备上生成一个公钥/私钥对,并将它们安全地存储在内部存储中,但我需要找到一种方法将生成的 Android 设备的公钥发送到我的服务器,所以我的服务器可以用它加密数据。

我不能简单地将公钥作为 HTTP 请求发送到我的服务器的原因是,我不希望有计算机的人能够生成公钥/私钥对并将其发送到我的服务器也能够接收数据作为。反编译Android应用程序或使用wireshark查找用于注册新密钥的url非常容易。

如果我要使用这种方式,我将如何在我的服务器端验证请求来自合法的 Android 设备?

2个回答

正如 Limit 所述,并且由于这个答案,正确的方法是:

  1. 在您的开发服务器上生成用于加密的密钥对

    openssl genrsa -des3 -out private.pem 2048

    openssl rsa -in private.pem -outform PEM -pubout -out public.pem

  2. 将公钥存储在应用程序代码中,无论在哪里。这将在第 5 步的函数中使用。可能会混淆代码以防止更改加密机制。(加密只会防止在传输过程中使用 burp proxy 之类的东西被篡改。smali 技能有人吗?)

  3. 在设备上生成一个密钥对(您提到您已将其关闭,但以防万一有人想知道)

    public static KeyPair getKeyPair() {
        KeyPair kp = null;
        try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
            kpg.initialize(2048);
            kp = kpg.generateKeyPair();
        } catch (Exception e) {
            e.printStackTrace();
        }
    
        return kp; 
    }
    
  4. 隔离公钥并将它们存储在您想要的位置。将私钥存储在安全存储中。

    KeyPair clientKeys = getKeyPair();
    publicKey = clientKeys.getPublic();
    
  5. 使用嵌入式开发公钥加密客户端公钥,以便您可以将其发送回家。

    public static String encryptToRSAString(String clearText, String publicKey) {
        String encryptedBase64 = "";
        try {
            KeyFactory keyFac = KeyFactory.getInstance("RSA");
            KeySpec keySpec = new X509EncodedKeySpec(Base64.decode(publicKey.trim().getBytes(), Base64.DEFAULT));
            Key key = keyFac.generatePublic(keySpec);
            // get an RSA cipher object and print the provider
            final Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");
            // encrypt the plain text using the public key
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] encryptedBytes = cipher.doFinal(clearText.getBytes("UTF-8"));
            encryptedBase64 = new String(Base64.encode(encryptedBytes, Base64.DEFAULT));
        } catch (Exception e) {
            e.printStackTrace();
        }
            return encryptedBase64.replaceAll("(\\r|\\n)", "");
    }
    

那么您是否只想从注册用户那里接收密钥?身份验证最终将需要您的服务器和注册用户之间的一些共享秘密。有了这个秘密,你可以尝试使用一些好的密钥交换协议来交换密钥,例如 EKE 或类似的协议。https://en.wikipedia.org/wiki/Encrypted_key_exchange

但是,如果你有服务器的 SSL 证书(我假设你有),当你与注册用户建立连接时,他可以在这个通道中向你发送他的公共对,因为它已经是加密连接。也被认为是更好的做法,不要开发/实施您自己甚至已知的安全协议,所以这个选项可能更好。

服务器的 SSL 证书是指由受信任的 CA 授权签署的证书