Java асимметричное шифрование: предпочтительный способ хранения публичных / частных ключей
-
27-09-2019 - |
Вопрос
Этот код генерирует пару публичных / частных клавиш:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
KeyPair keypair = keyGen.genKeyPair();
PrivateKey privateKey = keypair.getPrivate();
PublicKey publicKey = keypair.getPublic();
Что я хотел бы знать, это как вы обычно храните открытый ключ:
Вариант 1: Храните байты
byte[] privateKeyBytes = privateKey.getEncoded();
byte[] publicKeyBytes = publicKey.getEncoded();
// ... write to file
// convert bytes back to public/private keys
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
Что мне не нравится, это связать код конкретным реализациям, таким как PKCS8EncodedKeySpec
а также X509EncodedKeySpec
.
Вариант 2: Хранить модуль и экспоненту
KeyFactory fact = KeyFactory.getInstance("RSA");
RSAPublicKeySpec pub = fact.getKeySpec(publicKey, RSAPublicKeySpec.class);
RSAPrivateKeySpec priv = fact.getKeySpec(privateKey,RSAPrivateKeySpec.class);
// store modulus and exponent as BigIntegers
BigInteger modulus = pub.getModulus());
BigInteger exponent = pub.getPublicExponent());
// ... write to file
// recreate public key (the same applies to the private key)
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PublicKey pubKey = fact.generatePublic(keySpec);
Второй вариант легче реализовать, но я не знаю, может ли это быть менее исполнителем.
Любой совет?
Решение
В наших приложениях мы храним публичные и частные ключи в формате DER, поэтому их можно легко использовать и манипулировать за пределами Java. В нашем случае закрытые ключи не имеют паролей на них.
Чтобы преобразовать закрытый ключ к чему более легко использовать в Java:
openssl pkcs8 -topk8 -nocrypt -in key.pem -inform PEM -out key.der -outform DER
Тогда вы можете получить закрытый ключ RSA напрямую:
public static RSAPrivateKey getPrivateKey(File privateKeyFile) throws IOException, GeneralSecurityException {
byte[] keyBytes = new byte[(int)privateKeyFile.length()];
FileInputStream fis = new FileInputStream(privateKeyFile);
fis.read(keyBytes);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(spec);
return privKey;
}
Открытый ключ похож:
openssl rsa -in private.pem -pubout -outform DER -out public.der
И читать это:
public static RSAPublicKey getPublicKey(File publicKeyFile) throws IOException, GeneralSecurityException {
byte[] keyBytes = new byte[(int)publicKeyFile.length()];
FileInputStream fis = new FileInputStream(publicKeyFile);
fis.read(keyBytes);
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
RSAPublicKey pubKey = (RSAPublicKey)factory.generatePublic(publicKeySpec);
return pubKey;
}
Многие люди хранят затем ключевые магазины. Для наших целей нам нужен один и тот же ключ для совместного использования в нескольких приложениях на нескольких разных языках и не хотел дублировать файлы на диске.
В любом случае, производительность не должна быть огромной заботой, потому что вы, вероятно, будете хранить эти ключи в каком-то синглтоне или кэше вместо того, чтобы регенерировать их каждый раз.
Другие советы
Вы на самом деле храняте байты в обоих случаях, понимаете ли вы это или нет. Я полагаю, что правильный ответ намекается на @bris M. Carre ответ, который должен хранить объект более высокого уровня в его наиболее естественной форме. В случае публичных ключей очевидным вариантом является как PKCS # 1 RSAPUBLABLEYKEY ASN.1 Структура, Der-encoded или в качестве X509 CublicPublicKeyInfo Структура ASN.1, DER-encoded. Последнее - то, что дает вам поставщики Солнца, которые поддерживают Sun Class X509Encodedkeyspec. Точно так же PKCS8ENCODEDKEYSPEC поддерживает формат закрытого ключа. Оба эти форматы являются стандартами и поддерживаются OpenSSL, например. Солнце стремится - как правило :( - Чтобы поддержать существующие стандарты, а затем определить свои собственные.
Если вы хотите определить формат для хранения клавиш, то я бы выбрал формат, который расходуется, чтобы он не сломался, когда вы хотите изменить шифрование (когда старый получает слабый, например).
Поэтому я бы хранил байты, закодированные как Base64, вместе со строкой, которая описывает формат «RSA».