Хранение секретного ключа RSA Android

Во время создания простого приложения для обмена сообщениями Android, которое должно шифровать / дешифровать сообщения и отправлять их через Интернет, я решил использовать шифрование RSA с открытым / закрытым ключом. Вопрос в том, как сохранить закрытый ключ, чтобы, даже если телефон был злонамеренно укоренен, ключ оставался в безопасности? Насколько я понял, KeyStore используется для сертификатов, и не может быть использован для этого? Должен ли я зашифровать закрытый ключ как текстовый файл с AES? У меня очень мало опыта в сфере безопасности, поэтому, пожалуйста, не стесняйтесь исправлять мои идеи и высказать свое мнение!

С уважением.

3 ответа

Я думаю, что KeyStore может подойти для вашего использования. Он может хранить ключи RSA и шифровать их с помощью AES, поэтому даже при наличии корневого доступа их нельзя извлечь без пароля или взлома.

Здесь есть хороший пост об использовании KeyStore: http://nelenkov.blogspot.fr/2012/05/storing-application-secrets-in-androids.html

Вы можете сохранить свой открытый / закрытый ключ RSA, используя SharedPreference на Android. Чтобы сохранить ваши ключи в безопасности, когда телефон злонамеренно укоренен, вы можете выполнить следующие действия:

1: когда вы хотите зашифровать любые данные, сгенерируйте пару ключей.
2: запросить у пользователя пароль.
3: Используйте этот пароль для генерации симметричного ключа для шифрования вашего личного ключа.
4: Вы можете зашифровать свои данные с помощью открытого ключа и расшифровать с помощью личного ключа.
5. Вы можете сохранить сеанс для пароля, запрашиваемого на шаге 2. Во время этого сеанса вы можете использовать симметричный ключ (сгенерированный из пароля) для шифрования / дешифрования закрытого ключа.

Следующий фрагмент кода показывает, как хранить и извлекать открытый ключ

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;
}

В следующем фрагменте кода показано, как сохранить и получить закрытый ключ.

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;
}

Ни одно из хранилищ ключей (P12, JKS, AKS) в файловой системе не может быть достаточно безопасным для хранения закрытых ключей RSA. Только SmartCard или безопасные токены могут обеспечить высокий уровень безопасности. Прочитайте эту книгу: "Внутренние устройства безопасности Android". В этой книге вы найдете хорошее описание провайдеров Android Security и JCA.

Да, вы можете использовать KeyStore, чтобы сохранить свой RSA PrivateKey в Android Studio и при необходимости получить его для подписи. Основная идея заключается в том, что вы используете "AndroidKeystore" в качестве поставщика при создании ключей. Этот парень: /questions/10083314/ne-udalos-vyipolnit-operatsiyu-s-hranilischem-klyuchej-so-znakom-rsa-i-proverit имел важный момент - убедиться, что вы установили заполнение подписи. Это сработало для меня следующим образом:

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.";
    }
}
Другие вопросы по тегам