Как зашифровать закрытый ключ RSA с помощью PBE в формате PKCS # 5 на Java с помощью IAIK JCE? - PullRequest
0 голосов
/ 03 октября 2018

Я создал пару ключей RSA.Теперь я пытаюсь зашифровать закрытый ключ с помощью алгоритма DES, отформатировать его в PKCS # 5 и распечатать на консоли.К сожалению, сгенерированный закрытый ключ не работает.Когда я пытаюсь его использовать, после ввода правильной парольной фразы, клиент ssh возвращает неверную парольную фразу:

Загрузить ключ "test.key": неверная парольная фраза переданарасшифровать закрытый ключ

Может кто-нибудь подскажет, где я не прав?

Это код:

private byte[] iv;

public void generate() throws Exception {
    RSAKeyPairGenerator generator = new RSAKeyPairGenerator();
    generator.initialize(2048);
    KeyPair keyPair = generator.generateKeyPair();

    String passphrase = "passphrase";
    byte[] encryptedData = encrypt(keyPair.getPrivate().getEncoded(), passphrase);
    System.out.println(getPrivateKeyPem(Base64.encodeBase64String(encryptedData)));
}

private byte[] encrypt(byte[] data, String passphrase) throws Exception {
    String algorithm = "PBEWithMD5AndDES";
    salt = new byte[8];
    int iterations = 1024;

    // Create a key from the supplied passphrase.
    KeySpec ks = new PBEKeySpec(passphrase.toCharArray());
    SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm);
    SecretKey key = skf.generateSecret(ks);

    // Create the salt from eight bytes of the digest of P || M.
    MessageDigest md = MessageDigest.getInstance("MD5");
    md.update(passphrase.getBytes());
    md.update(data);
    byte[] digest = md.digest();
    System.arraycopy(digest, 0, salt, 0, 8);
    AlgorithmParameterSpec aps = new PBEParameterSpec(salt, iterations);

    Cipher cipher = Cipher.getInstance(AlgorithmID.pbeWithSHAAnd3_KeyTripleDES_CBC.getJcaStandardName());
    cipher.init(Cipher.ENCRYPT_MODE, key, aps);
    iv = cipher.getIV();
    byte[] output = cipher.doFinal(data);
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    out.write(salt);
    out.write(output);
    out.close();
    return out.toByteArray();
}

private String getPrivateKeyPem(String privateKey) throws Exception {
    StringBuffer formatted = new StringBuffer();
    formatted.append("-----BEGIN RSA PRIVATE KEY----- " + LINE_SEPARATOR);

    formatted.append("Proc-Type: 4,ENCRYPTED" + LINE_SEPARATOR);
    formatted.append("DEK-Info: DES-EDE3-CBC,");
    formatted.append(bytesToHex(iv));

    formatted.append(LINE_SEPARATOR);
    formatted.append(LINE_SEPARATOR);

    Arrays.stream(privateKey.split("(?<=\\G.{64})")).forEach(line -> formatted.append(line + LINE_SEPARATOR));
    formatted.append("-----END RSA PRIVATE KEY-----");

    return formatted.toString();
}

private String bytesToHex(byte[] bytes) {
    char[] hexArray = "0123456789ABCDEF".toCharArray();
    char[] hexChars = new char[bytes.length * 2];
    for (int j = 0; j < bytes.length; j++) {
        int v = bytes[j] & 0xFF;
        hexChars[j * 2] = hexArray[v >>> 4];
        hexChars[j * 2 + 1] = hexArray[v & 0x0F];
    }
    return new String(hexChars);
}

А это сгенерированный закрытый ключв формате PKCS # 5 PEM:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,CA138D5D3C048EBD

+aZNZJKLvNtlmnkg+rFK6NFm45pQJNnJB9ddQ3Rc5Ak0C/Igm9EqHoOS+iy+PPjx
pEKbhc4Qe3U0GOT9L5oN7iaWL82gUznRLRyUXtOrGcpE7TyrE+rydD9BsslJPCe+
y7a9LnSNZuJpJPnJCeKwzy5FGVv2KmDzGTcs9IqCMKgV69qf83pOJU6Dk+bvh9YP
3I05FHeaQYQk8c3t3onfljVIaYOfbNYFLZgNgGtPzFD4OpuDypei/61i3DeXyFUA
SNSY5fPwp6iSeSKtwduSEJMX31TKSpqWeZmEmMNcnh8oZz2E0jRWkbkaFuZfNtqt
aVpLN49oRpbsij+i1+udyuIXdBGRYt9iDZKnw+LDjC3X9R2ceq4AOdfsmEVYbO1i
YNms9eXSkANuchiI2YqkKsCwqI5S8S/2Xj76zf+pCDhCTYGV3RygkN6imX/Qg2eF
LOricZZTF/YPcKnggqNrZy4KSUzAgZ9NhzWCWOCiGFcQLYIo+qDoJ8t4FwxQYhx9
7ckzXML0n0q5ba5pGekLbBUJ9/TdtnqfqmYrHX+4OlrR7XAu478v2QH6/QtNKdZf
VRTqmKKH0n8JL9AgaXWipQstW5ERNZJ9YPBASQzewVNLv4gRZRTw8bYcU/hiPbWp
eqULYYI9324RzY3UTsz3N9X+zQsT02zNdxud7XmmoHL493yyvqT9ERmF4uckGYei
HZ16KFeKQXE9z+x0WNFAKX3nbttVlN5O7TAmUolFTwu11UDsJEjrYMZRwjheAZyD
UnV1LwhFT+QA0r68Mto3poxpAawCJqPP50V4jbhsOb0J7sxT8fo2mBVSxTdb9+t1
lG++x/gHcK51ApK1tF1FhRRKdtOzSib376Kmt23q0jVDNVyy09ys+8LRElOAY1Es
LIuMMM3F7l+F4+knKh3/IkPZwRIz3f9fpsVYIePPS1bUdagzNoMqUkTwzmq6vmUP
C5QvN6Z5ukVCObK+T8C4rya8KQ/2kwoSCRDIX6Mzpnqx6SoO4mvtBHvPcICGdOD6
aX/SbLd9J2lenTxnaAvxWW0jkF6q9x9AAIDdXTd9B5LnOG0Nq+zI+6THL+YpBCB9
6oMO4YChFNoEx0HZVdOc8E7xvXU2NqinmRnyh7hCR5KNfzsNdxg1d8ly67gdZQ1Q
bk1HPKvr6T568Ztapz1J/O6YWRIHdrGyA6liOKdArhhSI9xdk3H3JFNiuH+qkSCB
0mBYdS0BVRVdKbKcrk4WRHZxHsDsQn1/bPxok4dCG/dGO/gT0QlxV+hOV8h/4dJO
mcUvzdW4I8XKrX5KlTGNusVRiFX3Cy8FFZQtSxdWzr6XR6u0bUKS+KjDl1KoFxPH
GwYSTkJVE+fbjsSisQwXjWnwGGkNDuQ1IIMJOAHMK4Mly1jMdFF938WNY7NS4bIb
IXXkRdwxhdkRDiENSMXY8YeCNBJMjqdXZtR4cwGEXO+G+fpT5+ZrfPbQYO+0E0r4
wGPKlrpeeR74ALiaUemUYVIdw0ezlGvdhul2KZx4L82NpI6/JQ7shq9/BEW2dWhN
aDuWri2obsNL3kk2VBWPNiE6Rn/HtjwKn7ioWZ3IIgOgyavcITPBe0FAjxmfRs5w
VWLFBXqcyV9cu1xS4GoCNLk0MrVziUCwHmwkLIzQZos=
-----END RSA PRIVATE KEY-----

Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 04 октября 2018

Не существует такого понятия, как формат 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 или других реализаций.

0 голосов
/ 03 октября 2018

мистер

Я думаю, что перед вызовом шифрования вам необходимо расшифровать в два раза больше по соображениям безопасности.Вместо соли используйте также перец и соль.Не смешивайте алгоритм с aes256.

С уважением, Раджеш

...