Не существует такого понятия, как формат PKCS # 5.PKCS # 5 в первую очередь определяет две функции получения ключей на основе пароля и схемы шифрования на основе их использования, а также схему MAC на основе пароля, но не определяет никакого формата для данных.(Он определяет OID ASN.1 для этих операций и структуры ASN.1 для их параметров - в первую очередь PBKDF2 и PBES2, потому что единственным параметром для PBKDF1 и PBES1 является соль.) PKCS # 5 такжеопределяет схему заполнения для шифрования данных в режиме CBC;это заполнение было немного улучшено PKCS # 7 и использовалось многими другими приложениями, которые обычно называют это заполнением PKCS5 или дополнением PKCS7.Ни один из них не является форматом данных, и ни один из них не использует закрытые ключи RSA (или другие) как таковые.
Формат файла, который вы, очевидно, хотите, - это тот, который используется OpenSSH (долгое время всегда, затем дляпоследние несколько лет по умолчанию, пока OpenSSH 7.8 всего месяц назад не сделал его необязательным) и в результате также использовался другим программным обеспечением, которое хочет быть совместимым или даже взаимозаменяемым с OpenSSH.Этот формат фактически определяется OpenSSL, который OpenSSH долгое время использовал для большей части своей криптографии.(После Heartbleed OpenSSH создал форк OpenSSL под названием LibreSSL, который пытается быть более надежным и безопасным внутри, но намеренно поддерживает те же внешние интерфейсы и форматы, и в любом случае не получил широкого распространения.)
Это один из нескольких форматов 'PEM', определенных OpenSSL , и в основном он описан на странице руководства для ряда подпрограмм 'PEM', включая PEM_write[_bio]_RSAPrivateKey
- в вашей системе, если у вас есть OpenSSL и онне Windows, или в Интернете с частью шифрования в конце раздела 'ФОРМАТ ШИФРОВАНИЯ PEM', а подпрограмма EVP_BytesToKey, на которую она аналогичным образом ссылается на свою собственную страницу руководства .Вкратце: он не использует схему pbeSHAwith3_keyTripleDES-CBC (то есть SHA1), определенную PKCS # 12 / rfc7292 или схему pbeMD5withDES-CBC, определенную PKCS # 5 /rfc2898 в PBES1 .Вместо этого он использует EVP_BytesToKey
(то есть частично на основе PBKDF1) с итерацией md5 и 1 и солью, равной IV, для получения ключа, а затем шифрует / дешифрует с любым поддерживаемым режимом симметричного шифрования, которыйиспользует IV (то есть не потоковый или ECB), но обычно по умолчанию используется CBC DES-EDE3 (он же 3key-TripleDES), как вы просите.Да, EVP_BytesToKey с niter = 1 является плохим PBKDF и делает эти файлы незащищенными, если вы не используете очень надежный пароль;об этом уже есть множество вопросов.
И, наконец, открытый текст этого формата файла - это не кодировка PKCS # 8 (универсальная), возвращаемая [RSA]PrivateKey.getEncoded()
, аскорее формат только для RSA, определенный PKCS # 1 / rfc8017 et pred .Требуется пустая строка между заголовками Proc-type и DEK-info и base64, а также может потребоваться ограничитель строки в строке dashes-END в зависимости от того, какое программное обеспечение выполняет чтение.
Самый простой способДля этого нужно использовать программное обеспечение, уже совместимое с форматом (-ами) PEM закрытого ключа OpenSSL, включая сам OpenSSL.Java может запускать внешнюю программу: OpenSSH ssh-keygen
, если она у вас есть, или openssl genrsa
, если она у вас есть.Библиотека BouncyCastle bcpkix поддерживает этот и другие форматы OpenSSL PEM.Если «ssh client» - это jsch, то обычно читает файлы ключей в нескольких форматах, включая этот, но com.jcraft.jsch.KeyPairRSA
фактически поддерживает генерацию ключа и запись его также в этом формате PEM.Puttygen также поддерживает этот формат, но другие форматы, из которых он может конвертироваться, не являются дружественными для Java.Я уверен, что есть и другие.
Но если вам нужно сделать это в своем собственном коде, вот как:
// given [RSA]PrivateKey privkey, get the PKCS1 part from the PKCS8 encoding
byte[] pk8 = privkey.getEncoded();
// this is wrong for RSA<=512 but those are totally insecure anyway
if( pk8[0]!=0x30 || pk8[1]!=(byte)0x82 ) throw new Exception();
if( 4 + (pk8[2]<<8 | (pk8[3]&0xFF)) != pk8.length ) throw new Exception();
if( pk8[4]!=2 || pk8[5]!=1 || pk8[6]!= 0 ) throw new Exception();
if( pk8[7] != 0x30 || pk8[8]==0 || pk8[8]>127 ) throw new Exception();
// could also check contents of the AlgId but that's more work
int i = 4 + 3 + 2 + pk8[8];
if( i + 4 > pk8.length || pk8[i]!=4 || pk8[i+1]!=(byte)0x82 ) throw new Exception();
byte[] old = Arrays.copyOfRange (pk8, i+4, pk8.length);
// OpenSSL-Legacy PEM encryption = 3keytdes-cbc using random iv
// key from EVP_BytesToKey(3keytdes.keylen=24,hash=md5,salt=iv,,iter=1,outkey,notiv)
byte[] passphrase = "passphrase".getBytes(); // charset doesn't matter for test value
byte[] iv = new byte[8]; new SecureRandom().nextBytes(iv); // maybe SIV instead?
MessageDigest pbh = MessageDigest.getInstance("MD5");
byte[] derive = new byte[32]; // round up to multiple of pbh.getDigestLength()=16
for(int off = 0; off < derive.length; off += 16 ){
if( off>0 ) pbh.update(derive,off-16,16);
pbh.update(passphrase); pbh.update(iv);
pbh.digest(derive, off, 16);
}
Cipher pbc = Cipher.getInstance("DESede/CBC/PKCS5Padding");
pbc.init (Cipher.ENCRYPT_MODE, new SecretKeySpec(derive,0,24,"DESede"), new IvParameterSpec(iv));
byte[] enc = pbc.doFinal(old);
// write to PEM format (substitute other file if desired)
System.out.println ("-----BEGIN RSA PRIVATE KEY-----");
System.out.println ("Proc-Type: 4,ENCRYPTED");
System.out.println ("DEK-Info: DES-EDE3-CBC," + DatatypeConverter.printHexBinary(iv));
System.out.println (); // empty line
String b64 = Base64.getEncoder().encodeToString(enc);
for( int off = 0; off < b64.length(); off += 64 )
System.out.println (b64.substring(off, off+64<b64.length()?off+64:b64.length()));
System.out.println ("-----END RSA PRIVATE KEY-----");
Наконец, формат OpenSSL требует, чтобы шифрование IV и соль PBKDF были одинаковыми, и это делает это значение случайным, как и я.Вычисленное значение, которое вы использовали только для соли, MD5 (пароль || data), немного напоминает конструкцию синтетического IV (SIV), которая теперь принята для использования с шифрованием, но это не то же самое, плюс я не знаю,Любой компетентный аналитик рассмотрел случай, когда SIV также используется для соли PBKDF, поэтому я не хотел бы полагаться на этот метод здесь.Если вы хотите спросить об этом, это не совсем вопрос программирования, и он больше подходит для cryptography.SX или, возможно, security.SX.
добавлено для комментариев:
ЭтоВывод кода работает для меня с puttygen от 0.70, как на Windows (от upstream = chiark), так и на CentOS6 (от EPEL).Согласно источнику, сообщение об ошибке, которое вы дали, появляется, только если cmdgen вызвал key_type в sshpubk.c, который распознал первую строку как начинающуюся с «----- BEGIN», но не «----- BEGIN OPENSSH PRIVATE KEY»(это совсем другой формат), затем через import_ssh2 и openssh_pem_read с именем load_openssh_pem_key в файле import.c, который НЕ находит первую строку, начинающуюся с «----- BEGIN» и заканчивающуюся «PRIVATE KEY -----».Это очень странно, потому что оба этих ПЛЮС "RSA" между ними генерируются моим кодом и , которые необходимы OpenSSH (или openssl) для его принятия.Попробуйте посмотреть на каждый байт первой строки как минимум (может быть, первые две строки) с чем-то вроде cat -vet
или sed -n l
или в крайнем случае od -c
.
RFC 2898 сейчас довольно стар;Хорошая практика сегодня обычно составляет от 10 до 100 тысяч итераций, и лучше не использовать итеративный хеш, а вместо этого что-то с нехваткой памяти, как scrypt или Argon2.Но, как я уже писал, унаследованное PEM-шифрование OpenSSL, которое было разработано еще в 1990-х годах, использует одну итерацию (un, eine, 1) и, следовательно, является схемой POOR и INSECURE.Никто не может изменить это сейчас, потому что так оно и было задумано.Если вы хотите достойный PBE, не используйте этот формат.
Если вам нужен ключ только для SSH: OpenSSH (уже несколько лет) поддерживает и последние версии Putty (gen) могут импортировать определенный OpenSSH «новый формат», который использует bcrypt, но jsch можетт.OpenSSH (используя OpenSSL) также может читать (PEM) PKCS8, что позволяет PBKDF2 (лучше, но не лучше) с итерациями по желанию, и это похоже на jsch, но не Putty (gen).Я не знаю для Cyberduck или других реализаций.