Преобразование генератора открытого ключа Java DH в Node JS

У меня есть такая функция:

public String generatePublicKeyEncoded() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
    Serializable serializable = new SecureRandom();
    BigInteger bigInteger = BigInteger.probablePrime(1024, (Random) serializable);
    serializable = BigInteger.probablePrime(1024, (Random) serializable);
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DH");
    keyPairGenerator.initialize(new DHParameterSpec(bigInteger, (BigInteger)serializable));
    this.keyPair = keyPairGenerator.generateKeyPair();
    return HexEncoder.encode(this.keyPair.getPublic().getEncoded());
}

и я пытаюсь преобразовать в node. Мой код node.js:

const prime_length = 1024;
const diffHell = crypto.createDiffieHellman(prime_length);
diffHell.generateKeys('base64');
const hexPublicKey = diffHell.getPublicKey('hex')

но мой сгенерированный Java открытый ключ

308201A33082011706092A864886F70D0103013082010802818100DE4779E7F4523CA143FFE102853E671CAAAB96203B1FC3C42D0EA1CB6878FCA889C79C709DDB1190DF9073050B1AD410D34A48A6E5A1D2C1854C471528DB3C4FE48A237FC86BAA777AAB8A17750DBA7948F258BD55E480BA3FFD87076BC4B0429CE731E31A8320DC594F9BD5022CD203C95D73F5B3E91C930A0AF2FA7AEE160502818100D719835971E8A91980141201FF765392A0049841142A3C203862AF8FFBC719528F142706639BD0C614EBA72660876F5A7011B5FC08224824577324FCF847648F24A600F408BED17770AAF958CC75076164DAA5E6179BFC573F40E2B086FC18A48B67A10F7B9B7C037A7BEEEDF554764CC8653C09AA3D330CC3C30F89616D810703818500028181008EE027B916FC87BE2627CFB53F4DA76693A06EECAC8DA2A6B9155C66D60BCD9977A811B3732F72880BDE1AA259731FE37AD4284909481777444F7A3C5BCF7F287AD5F05BE45F4553CC06D599E7E3BAD6736D6BCA59EAD8B0F6C0FE980F471304AC2600A677A70CE46F2835FA6797D18FAA8A237573916E604AF40CA456CCEE1E

и ключ, сгенерированный узлом:

1b6254629d00a18333ec701558ef34b0df9b86569985799106c4d71d1fabd3c41ef25c7bf4a522498a92c983ca09e3435ebd09b51220d6ffccb296803f1718bf8cf7e0f72432b65b60d8a49d6d80fec6e708a88d2b00e2829e74534fa86a94d96a743725c6eb2076d5ac03edd909491639a359467a67fc64b9dc2fb420d822a2

Где ошибка?

Класс HexEncoder:

public class HexEncoder {

    private static final byte[] a = { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70 };

    public static String encode(byte[] paramArrayOfByte)
    {
        return encode(paramArrayOfByte, false, 0);
    }

public static String encode(byte[] paramArrayOfByte, boolean paramBoolean, int paramInt)
    {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < paramArrayOfByte.length; i++)
        {
            if ((paramBoolean) && (i > 0) && (i % paramInt == 0)) {
                stringBuilder.append("\r\n");
            }
            int j = paramArrayOfByte[i];
            int k = j;
            if (j < 0) {
                k = j + 256;
            }
            j = k / 16;
            stringBuilder.append((char)a[j]);
            stringBuilder.append((char)a[(k % 16)]);
        }
        return stringBuilder.toString();
    }

}


person polonio    schedule 11.11.2020    source источник
comment
откуда вы импортируете HexEncoder?   -  person Vishrant    schedule 11.11.2020
comment
Это класс.. извините, я забыл отправить вопрос   -  person polonio    schedule 11.11.2020
comment
Я не ожидаю этого, но сгенерированное простое число отличается в обоих случаях.   -  person Vishrant    schedule 11.11.2020
comment
Почему всегда будет то же самое? Вы генерируете значения случайным образом.   -  person President James K. Polk    schedule 11.11.2020
comment
Проблема в размере... Java намного больше узла, и я использую этот сгенерированный ключ для отправки в качестве параметра тела в конечную точку... и когда я пытаюсь сгенерировать с помощью узла, он возвращает ошибку в запросе конечной точки   -  person polonio    schedule 11.11.2020


Ответы (1)


Во-первых, ваша генерация параметров DH в Java ужасно неверна. Модуль (p) и генератор (g) для DH не просто большие случайные простые числа. p должно быть большим простым числом и не обязательно должно быть случайным, но ДОЛЖНО иметь мультипликативную группу (Zp*) с «негладким» порядком, то есть простая факторизация порядка не должна состоять полностью из малых множителей, или, точнее, порядок должен иметь большой простой множитель. Поскольку порядок мультипликативной группы для простого p равен p-1, это обычно достигается путем выбора безопасного простого числа (p=2q+1) или простого числа Шнорра (p=kq+1), где q — достаточно большое простое число. OTOH g не обязательно должен быть большим (и НЕ ДОЛЖЕН быть больше или равен p, что может случиться с вашим кодом) и не должен быть случайным, но должен генерировать подгруппу достаточного размера (обычно q). Выбор параметров, как вы это сделали, иногда полностью терпит неудачу (согласование ключей не сработает), но в большинстве случаев это просто небезопасно — общее значение, которое должно было быть секретным, может быть легко определено злоумышленником и использовано для раскрытия и/ или изменить ваши якобы безопасные данные. Интересно, не перепутали ли вы или кто-то другой DH с RSA (или, что менее вероятно, Рабином), которые сильно отличаются друг от друга, хотя все они связаны с большими числами и математикой.

Но это оффтоп для SO. См. Википедию и многочисленные вопросы и ответы. на security.SX и crypto.SX. По вашему актуальному вопросу:

Это кодировка. Для (всех) объектов Java PublicKey функция getEncoded() возвращает формат 'SubjectPublicKeyInfo', определенный в X.509 (и более удобно повторенный в RFC5280, также известном как PKIX, связанный там), который основан на ASN.1 и DER. В частности, он содержит OID, идентифицирующий алгоритм как DH, структуру параметров, содержащую p и g, и фактическое значение открытого ключа (обычно обозначаемое y), заключенное в BITSTRING; для значения, которое вы разместили:

$ openssl asn1parse -i -dump -inform d <64790186.bin
    0:d=0  hl=4 l= 419 cons: SEQUENCE
    4:d=1  hl=4 l= 279 cons:  SEQUENCE
    8:d=2  hl=2 l=   9 prim:   OBJECT            :dhKeyAgreement
   19:d=2  hl=4 l= 264 cons:   SEQUENCE
   23:d=3  hl=3 l= 129 prim:    INTEGER           :DE4779E7F4523CA143FFE102853E671CAAAB96203B1FC3C42D0EA1CB6878FCA889C79C709DDB1190DF9073050B1AD410D34A48A6E5A1D2C1854C471528DB3C4FE48A237FC86BAA777AAB8A17750DBA7948F258BD55E480BA3FFD87076BC4B0429CE731E31A8320DC594F9BD5022CD203C95D73F5B3E91C930A0AF2FA7AEE1605
  155:d=3  hl=3 l= 129 prim:    INTEGER           :D719835971E8A91980141201FF765392A0049841142A3C203862AF8FFBC719528F142706639BD0C614EBA72660876F5A7011B5FC08224824577324FCF847648F24A600F408BED17770AAF958CC75076164DAA5E6179BFC573F40E2B086FC18A48B67A10F7B9B7C037A7BEEEDF554764CC8653C09AA3D330CC3C30F89616D8107
  287:d=1  hl=3 l= 133 prim:  BIT STRING
      0000 - 00 02 81 81 00 8e e0 27-b9 16 fc 87 be 26 27 cf   .......'.....&'.
      0010 - b5 3f 4d a7 66 93 a0 6e-ec ac 8d a2 a6 b9 15 5c   .?M.f..n.......\
      0020 - 66 d6 0b cd 99 77 a8 11-b3 73 2f 72 88 0b de 1a   f....w...s/r....
      0030 - a2 59 73 1f e3 7a d4 28-49 09 48 17 77 44 4f 7a   .Ys..z.(I.H.wDOz
      0040 - 3c 5b cf 7f 28 7a d5 f0-5b e4 5f 45 53 cc 06 d5   <[..(z..[._ES...
      0050 - 99 e7 e3 ba d6 73 6d 6b-ca 59 ea d8 b0 f6 c0 fe   .....smk.Y......
      0060 - 98 0f 47 13 04 ac 26 00-a6 77 a7 0c e4 6f 28 35   ..G...&..w...o(5
      0070 - fa 67 97 d1 8f aa 8a 23-75 73 91 6e 60 4a f4 0c   .g.....#us.n`J..
      0080 - a4 56 cc ee 1e                                    .V...
# if you know DER you can see the value in the BITSTRING is 
# the encoding of a 128-octet INTEGER, like the two in the parameters structure

nodejs crypto, хотя на самом деле использует OpenSSL внутри, который поддерживает формат X.509/PKIX SPKI, не использует этот формат. Он возвращает только фактическое значение открытого ключа (y). Чтобы создать тот же стандартный формат, что и в Java, сделайте следующее:

const crypto = require('crypto');

function der(tag,val){ // for basic tags and up to 64kB, which are enough here
  var len = val.length;
  var enc = Buffer.alloc(4); enc[0]=tag;
  if( len < 128 ){ enc[1]=len; enc = enc.slice(0,2); }
  else if(len < 256 ){ enc[1]=0x81; enc[2]=len; enc = enc.slice(0,3); }
  else{ enc[1]=0x82; enc[2]=len>>8; enc[3]=len&0xFF; }
  return Buffer.concat([enc,val]);
}
function derpint(x){ return der(0x02, x[0]<128? x: Buffer.concat([onezero,x])); }
const onezero = Buffer.alloc(1,0);
function derseq(x){ return der(0x30, Buffer.concat(x)); }
const oidpkcs3 = Buffer.from('06092a864886f70d010301','hex');

var dh = crypto.createDiffieHellman(1024);
var pub = dh.generateKeys(); 
var p = dh.getPrime(), g = dh.getGenerator();

var algid = derseq([oidpkcs3,derseq([derpint(p),derpint(g)])]);
var spki = derseq([algid,der(0x03,Buffer.concat([onezero,derpint(pub)]))]);
console.log(spki.toString('hex'));

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

для закрытых ключей Java также использует общий формат, но другой общий формат, как указано на той же странице Javadoc для Key, а именно PrivateKeyInfo из PKCS8 более удобно доступен как RFC5208. У него есть номер версии, тот же алгид (OID+параметры), что и у SPKI, и частное значение (обозначаемое как x), завернутое в OCTETSTRING вместо BITSTRING, поэтому замените или добавьте последние две строки выше:

var prv = dh.getPrivateKey(); 
var pkcs8 = derseq([derpint(onezero),algid,der(0x04,derpint(prv))]);
console.log(pkcs8.toString('hex'));
person dave_thompson_085    schedule 11.11.2020
comment
Мужик, ты гений. Спасибо за объяснение - person polonio; 12.11.2020
comment
@ dave_thompson_085: Я всегда думал, что правильная генерация ключей DH на стороне Java выполняется с помощью 1024 или 2048, представленных генератору ключей. Вы пишете, что Java-генерация параметров DH ужасно неверна, но я искал несколько часов, но не нашел ни одного примера Java, который соответствовал бы вашим мыслям относительно генерации модуля и базы (простого числа). Не могли бы вы поделиться ссылкой на правильное crptographic решение? Спасибо. - person Michael Fehr; 12.11.2020
comment
что означает 06092a864886f70d010301? - person polonio; 12.11.2020
comment
@MichaelFehr: процесс генерации состоит из двух частей: 1) генерация параметров домена g и p только один раз и 2) генерация закрытого ключа и соответствующего открытого ключа каждый раз. dave_thompson имел в виду #1, когда сказал это. Вы можете позволить Java выполнить # 1, используя AlgorithmParameterGenerator вместе с DHGenParameterSpec, но вместо этого OP просто выбирает два случайных простых числа. - person President James K. Polk; 12.11.2020
comment
В этом решении есть ли способ получить PrivateKey? это просто dh.getPrivateKey() ?? - person polonio; 13.11.2020
comment
@MichaelFehr+, если вы используете KeyPairGenerator/*forDH*/.initialize(int nbits, SecureRandom r), он либо использует предопределенные параметры для «стандартных» размеров, либо генерирует простое число Шнорра и генератор (один вариант, который я упомянул), используя ту же логику, что и для DSA в FIPS 186; видеть, что. Или см. github.com/bcgit/bc-java/blob/master/core/src/main/java/org/ для метода безопасного простого заполнения. - person dave_thompson_085; 13.11.2020
comment
@gara: значение, начинающееся с 0609, представляет собой закодированный OID (также известный как идентификатор объекта) для DH, или, точнее, DH типа PKCS3, который используется в большинстве систем, включая Java. Кодирование OID в DER сложное, и я не хотел вникать во всю логику, когда нужен только один случай. Да, (nodejs crypto) dh.getPrivateKey — это только частное значение (x); как и открытый ключ, если закрытый ключ является внешним, в него обычно добавляются параметры и другая информация, и здесь Java использует стандарт PKCS8, который по своей концепции аналогичен стандарту X.509 SPKI для открытого ключа. - person dave_thompson_085; 13.11.2020
comment
@dave_thompson_085 Я обнаружил, что если я пишу dh.getPublicKey(), результат вставляется в переменную spki... это шестнадцатеричная переменная spki p + g + y, поэтому шестнадцатеричная строка больше, чем dh.getPublicKey ().toString('hex')' и publicKey находится внутри spki? Если мне нужно сделать то же самое с privateKey, что мне нужно изменить в вашем примере node js? - person polonio; 13.11.2020
comment
@dave_thompson_085 Мне нужно использовать закрытый ключ в моем коде, но я попытался сгенерировать его, просто изменив вашу логику в этой строке: var spki = derseq([algid,der(0x03,Buffer.concat([onezero,derpint(pub) ])))]); , я изменил pub на dh.getPrivateKey(), но сгенерированный privateKey был недействителен для использования в следующем запросе... Что мне нужно изменить? - person polonio; 17.11.2020
comment
@polonio: зависит от того, что вы подразумеваете под «+». SPKI для (PKCS3)DH содержит p,g,y, но не просто объединяет p,g,y. Для частного, см. мое редактирование. - person dave_thompson_085; 17.11.2020