создать java PrivateKey и PublicKey из строки файла - PullRequest
0 голосов
/ 28 мая 2020

Добрый день,

Есть еще одна третья сторона, которой нужно мое веб-приложение для отправки им данных в зашифрованном формате. Поэтому они присылают мне какое-то руководство для этого, однако я не знаком с ним, я пытаюсь погуглить, но похоже, что я ошибаюсь.

Руководство выглядит примерно так:

  1. Запустите команду openssl для генерации приватного ключа:

    openssl ecparam -name prime256v1 -genkey -out myprivate.pem

После запуска этого Я вывожу файл priv.pem и увидел, что внутри есть ключ, заканчивающийся на '==', который выглядит следующим образом:

-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEILefWfeuZOgnbDlxpwo3uQ2xQXfhXHUPTS+vKzvVZdCToAoGCCqGSM49
AwEHoUQDQgAE4MeQspGRJ1qdpweBfiaT5P84alZdga1f7mSpa5HqXTH58u0ZWJUQ
J7ToU/bUOPITh4FX07AV6wrgFCmwtUenDQ==
-----END EC PRIVATE KEY-----

Второй запускает команду openssl для генерации ключа publi c, а затем отправляет их:

openssl e c -in myprivate.pem -pubout -out mypubli c .pem

Преобразование закрытого ключа в формат pkcs8:

openssl pkcs8 -topk8 -nocrypt -in myprivate.pem -out mypkcs8.pem

Третья сторона предоставит мне ключ publi c в строковом формате, затем попросит меня сгенерировать секретный ключ и предоставит мне код java, как показано ниже:

сначала создается секретный ключ, а второй зашифрован:

public static SecretKey generateSharedSecret(PrivateKey privateKey,
            PublicKey publicKey) {
        try {
            KeyAgreement keyAgreement = KeyAgreement.getInstance( "ECDH" );
            keyAgreement.init( privateKey );
            keyAgreement.doPhase( publicKey, true );
            SecretKeySpec key = new SecretKeySpec(
                    keyAgreement.generateSecret( ), "AES" );
            return key;
        } catch ( Exception e ) {
            // TODO Auto-generated catch block
            e.printStackTrace( );
            return null;
        }
    }

public static String encryptString(SecretKey key, String plainText) {
        try {
            String myIv = "Testing @ IV!";
            byte[] iv = myIv.getBytes( "UTF-8" );
            IvParameterSpec ivSpec = new IvParameterSpec( iv );
            Cipher cipher = Cipher.getInstance( "AES / CBC / PKCS5Padding" );
            byte[] plainTextBytes = plainText.getBytes( "UTF-8" );
            byte[] cipherText;
            cipher.init( Cipher.ENCRYPT_MODE, key, ivSpec );
            cipherText = new byte[cipher.getOutputSize( plainTextBytes.length )];
            int encryptLength = cipher.update( plainTextBytes, 0,
                    plainTextBytes.length, cipherText, 0 );
            encryptLength += cipher.doFinal( cipherText, encryptLength );
            return bytesToHex( cipherText );
        } catch ( Exception e ) {
            e.printStackTrace( );
            return null;
        }
    }

, а также метод байтов в шестнадцатеричную строку:

public static String bytesToHex(byte[] byteArray) {
    StringBuffer hexStringBuffer = new StringBuffer( );
    for ( int i = 0; i < byteArray.length; i++ ) {
        hexStringBuffer.append( String.format( "%02X", byteArray[ i ] ) );
    }
    return hexStringBuffer.toString( );
}

У меня есть собственный закрытый ключ а также ключ publi c с помощью команды openssl, но 4-й шаг говорит мне, что они также дадут мне ключ publi c, поэтому я не понимаю, какой ключ publi c мне следует использовать.

А также, как я могу преобразовать String в java PrivateKey и PublicKey объект?

* добавить * I tr y, чтобы преобразовать файл der в объект java PublicKey, похоже, работает. Перед этим я конвертирую pem в der с помощью команды openssl:

openssl pkey -pubin -in ecpubkey.pem -outform der -out ecpubkey.der

Вот код java:

        File f = new File("/home/my/Desktop/key/ecpubkey.der");
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        byte[] keyBytes = new byte[(int) f.length()];
        dis.readFully(keyBytes);
        dis.close();

        KeyFactory fact = KeyFactory.getInstance("EC");
        PublicKey theirpub = fact.generatePublic(new X509EncodedKeySpec(keyBytes));

Однако я нажимаю java.security.spec.InvalidKeySpecException: java.io.IOException: insufficient data, когда пытаюсь чтобы преобразовать файл der в объект java PrivateKey, я сделал следующее:

openssl ecparam -name prime256v1 -genkey -out priv.pem
openssl pkcs8 -topk8 -nocrypt -in priv.pem -outform der -out priv.der

И вот мой код java:

        File f2 = new File("/home/my/Desktop/key/priv.der");
        FileInputStream fis2 = new FileInputStream(f2);
        DataInputStream dis2 = new DataInputStream(fis2);
        byte[] keyBytes2 = new byte[(int) f.length()];
        dis2.readFully(keyBytes2);
        dis2.close();

        KeyFactory fact2 = KeyFactory.getInstance("EC");
        PrivateKey pKey = fact2.generatePrivate( new PKCS8EncodedKeySpec(keyBytes2) ); // this line hit insufficient data

Ответы [ 2 ]

2 голосов
/ 28 мая 2020

Diff ie - Хеллман хорошо объяснен в wikipedia - и, вероятно, в некоторых из сотен вопросов, приведенных здесь, а также о crypto.SX и security.SX, но я не могу легко найти какой. Вкратце:

  • вы создаете пару ключей, сохраняете свой закрытый ключ и предоставляете свой открытый ключ другой стороне

  • другая сторона делает то же самое вещь (или ее отражение): сгенерируйте пару ключей, сохраните их закрытый ключ и предоставьте вам свой открытый ключ

  • вы используете свой закрытый ключ и их открытый ключ для вычисления значения «соглашения»

  • они аналогичным образом используют свой закрытый ключ и ваш открытый ключ для вычисления того же значение «договор». Это также называется общим секретом, потому что вы и другая сторона знаете об этом, но любой, кто подслушивает ваш трафик c, не знает.

«Предоставить» в этом синопсисе опускает много очень важных деталей. Жизненно важно, чтобы, когда вы предоставляете свой публичный ключ другой стороне, они фактически получали ваш публичный ключ, а не значение, измененное или замененное противником, и аналогично, когда они предоставляют вам свой публичный ключ, жизненно важно, чтобы вы получили настоящий, а не измененный или поддельный. Именно здесь настоящие системы ЦТ в основном выходят из строя, и тот факт, что вы не упоминаете ни о каких защитных механизмах или осложнениях, необходимых здесь, предполагает, что ваша схема будет небезопасной и легко взломанной - если она будет использована для чего-то, что стоит украсть.

Обратите внимание, что вы НИКОГДА не должны раскрывать или «посылать» свой личный ключ кому-либо, и они также не должны раскрывать свой. Это основная основа для криптографии publi c -key (или 'asymmetri c'), которая может иметь любую ценность или вообще использоваться.

Существует множество способов представления ключей, но только некоторые имеют отношение к вам.

Publi c ключи часто представлены либо в

  • структуре ASN.1 SubjectPublicKeyInfo определено в X.509 и более удобно в PKIX, в основном в rfc5280 # 4.1 и # 4.1.2.7 и rfc3279 2.3 , закодировано в DER , который имеет ограничение, заключающееся в том, что многие байты, используемые в этой кодировке, не являются допустимыми символами и не могут быть правильно отображены или обработаны иным образом, а иногда не переданы или даже сохранены; или

  • та же самая структура DER ASN.1 'завернутая' в формат 'PEM' , который преобразует проблемные двоичные данные во все отображаемые персонажи в легко управляемой форме. Формат PEM был первоначально создан для вызова схемы защищенной электронной почты Privacy Enhanced Mail , который отошел на второй план и был заменен другими схемами и технологиями, но определенный в нем формат все еще используется. Формат publickey PEM был недавно повторно стандартизирован на rfc7468 # 13 (который, как вы видите, ссылается на rfc5280).

OpenSSL поддерживает оба из них, но утилита командной строки, которую вы используете в основном , по умолчанию использует PEM - и поскольку вам нужно передать свой ключ «им» , и им необходимо передать вам свой ключ, PEM вполне может быть самым надежным и / или удобным способом сделать это. (Хотя возможны и другие форматы, если вы и они согласны - и если они требуют чего-то еще, вам придется согласиться, чтобы эта схема вообще работала.)

Java напрямую поддерживает только DER, таким образом, предполагая, что вы получили их открытый ключ в SPKI PEM, чтобы использовать его в Java, вам необходимо преобразовать его в DER. Вы можете сделать это в OpenSSL

openssl pkey -pubin -in theirpub.pem -outform der -out theirpub.der

, а затем прочитать DER в Java crypto KeyFactory:

byte[] theirpubder = Files.readAllBytes(Paths.get(whatever));
KeyFactory fact = KeyFactory.getInstance("EC");
PublicKey theirpub = fact.generatePublic(new X509EncodedKeySpec(theirpubder));
// can downcast to ECPublicKey if you want to be more specific

В качестве альтернативы вы можете Java преобразовать PEM, который не не слишком сложно; есть несколько вариантов, но мне нравится:

String theirpubpem = new String(Files.readAllBytes(Paths.get(whatever)));
// IN GENERAL letting new String(byte[]) default the charset is dangerous, but PEM is OK
byte[] theirpubder = Base64.getMIMEDecoder().decode(theirpubpem.replaceAll("-----[^\\n]*\\n","") );
// continue as for DER

Для закрытых ключей существует значительно больше представлений, но только одно (или два-i sh), которое Java разделяет с OpenSSL. Поскольку вам нужно только хранить закрытый ключ локально, а не «отправлять» его, PEM может не понадобиться; если это так, вы можете просто добавить -outform der к своей команде pkcs8 -topk8 -nocrypt, соответствующим образом изменив имя, и прочитать результат непосредственно в Java KeyFactory таким же образом, как указано выше, за исключением PKCS8EncodedKeySpec и generatePrivate и [EC]PrivateKey. Если вы действительно хотите сохранить его в (PKCS8-clear) PEM, вы также можете комбинировать вышеуказанное. нестандартный и обычно не считается хорошей практикой, хотя для ECDH с prime256v1 (он же secp256r1 или P-256) это технически возможно. AFAIK все хорошие стандарты используют промежуточный этап получения ключа (он же Key Derivation Function или KDF). Поскольку вы не показали нам их «руководство», я не могу сказать, правильно ли это - по крайней мере, для небольших значений правильности.

Чтобы вы знали, используйте CB C с фиксированным IV более одного раза для одного и того же ключа (который в данном случае является тем же результатом DH) небезопасен. Я предполагаю, что «Тестирование» означает, что вы планируете заменить его чем-то лучшим.

Также, к вашему сведению, вам не нужно , чтобы использовать все усложнение API Cipher.init,update,doFinal. Когда данные достаточно малы, чтобы поместиться в памяти, как здесь, вы можете просто сделать:

cipher.init(ENCRYPT_MODE, key, parms);
byte[] encrypted = cipher.doFinal (plainbytes);
// or since you want to hexify it
... bytesToHex (cipher.doFinal (plainbytes)) ...

Наконец, поскольку Java byte подписан, ваш bytesToHex выведет почти ровно половину всех байты с префиксом FFFFFF. Это очень необычно и феноменально некрасиво, но, опять же, я не знаю, «правильно» ли это для вас.

0 голосов
/ 29 мая 2020

Основываясь на объяснении и коде dave_thompson_085, мне удалось создать свои java PublicKey и Privatekey со следующим:

public static PublicKey getPublicKey(String filename) throws IOException, GeneralSecurityException {
    String publicKeyPEM = getKey(filename);
    return getPublicKeyFromString(publicKeyPEM);
}

private static String getKey(String filename) throws IOException {
    // Read key from file
    String strKeyPEM = "";
    BufferedReader br = new BufferedReader(new FileReader(filename));
    String line;
    while ((line = br.readLine()) != null) {
        strKeyPEM += line + "\n";
    }
    br.close();
    return strKeyPEM;
}

public static PublicKey getPublicKeyFromString(String key) throws IOException, GeneralSecurityException {
    String publicKeyPEM = key;
    publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
    publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
    BASE64Decoder b = new BASE64Decoder();
    byte[] encoded = b.decodeBuffer(publicKeyPEM);
    KeyFactory kf = KeyFactory.getInstance("EC");
    PublicKey pubKey = (PublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
    return pubKey;
}

, и это для закрытого ключа

public static PrivateKey getPrivateKey(String filename) throws IOException, GeneralSecurityException {
    String privateKeyPEM = getKey(filename);
    return getPrivateKeyFromString(privateKeyPEM);
}

public static PrivateKey getPrivateKeyFromString(String key) throws IOException, GeneralSecurityException {
    String privateKeyPEM = key;
    privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----\n", "");
    privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----", "");

    BASE64Decoder b = new BASE64Decoder();
    byte[] encoded = b.decodeBuffer(privateKeyPEM);
    KeyFactory kf = KeyFactory.getInstance("EC");
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
    PrivateKey privKey = (PrivateKey) kf.generatePrivate(keySpec);
    return privKey;
}

Большое спасибо к объяснению @ dave_thompson_085.

...