2013-11-11 9 views
5

Я пишу тестовый жгут в java для программы, связанной с протоколом ikev2. В рамках этого я должен уметь вычислять подпись ECDSA (в частности, используя кривую NIST P-256).Вычисление подписи ECDSA на Java в соответствии с векторным тестом RFC

RFC 4754 Описывает использование ECDSA в IKEv2 и предоставляет набор тестовых векторов (в том числе для кривой p256, которая мне нужна).

Я пытаюсь запустить значения ECDSA-256 Test Vector (Section 8.1 в RFC) посредством реализации подписи ECDSA Java, используя следующий код:

//"abc" for the input 
byte[] input = { 0x61, 0x62, 0x63 }; 

//Ugly way of getting the ECParameterSpec for the P-256 curve by name as opposed to specifying all the parameters manually. 
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); 
ECGenParameterSpec kpgparams = new ECGenParameterSpec("secp256r1"); 
kpg.initialize(kpgparams); 
ECParameterSpec params = ((ECPublicKey) kpg.generateKeyPair().getPublic()).getParams(); 

//Create the static private key W from the Test Vector 
ECPrivateKeySpec static_privates = new ECPrivateKeySpec(new BigInteger("DC51D3866A15BACDE33D96F992FCA99DA7E6EF0934E7097559C27F1614C88A7F", 16), params); 
KeyFactory kf = KeyFactory.getInstance("EC"); 
ECPrivateKey spriv = (ECPrivateKey) kf.generatePrivate(static_privates); 

//Perfrom ECDSA signature of the data with SHA-256 as the hash algorithm 
Signature dsa = Signature.getInstance("SHA256withECDSA"); 
dsa.initSign(spriv); 
dsa.update(input); 

byte[] output = dsa.sign(); 
System.out.println("Result: " + new BigInteger(1, output).toString(16)); 

Результат должен быть:

CB28E099 9B9C7715 FD0A80D8 E47A7707 9716CBBF 917DD72E 97566EA1 C066957C 86FA3BB4 E26CAD5B F90B7F81 899256CE 7594BB1E A0C89212 748BFF3B 3D5B0315

Вместо этого я получаю:

30460221 00dd9131 edeb5efd c5e718df c8a7ab2d 5532b85b 7d4c012a e5a4e90c 3b824ab5 d7022100 9a8a2b12 9e10a2ff 7066ff79 89aa73d5 ba37c868 5ec36517 216e2e43 ffa876d7

Я знаю, что разница в длине из-за Java ASN. 1 Кодирование подписи. Тем не менее, остальная часть этого совершенно неправа, и я в тупике, почему.

Любая помощь или совет были бы очень признательны!

PS Я не являюсь экспертом крипто ECDSA или Java, так что, вероятно, глупая ошибка, которую я делаю

ответ

11

Я предполагаю, что каждый раз, когда вы запускаете программу, вы получите другое значение подписи для того же открытого текста (to-be-signed).

ECDSA указывает, что на подпись должен быть сформирован случайный эфемерный закрытый ключ ECDSA. С этой целью Signature.getInstance("SHA256withECDSA") не позволяет указать эфемерный ключ (это хорошая вещь, чтобы не допустить многих выстрелов в ногу!). Вместо этого он получает свой собственный экземпляр SecureRandom, который сделает ваш вывод недетерминированным.

Это, вероятно, означает, что вы не можете использовать JCE (Signature.getInstance()) для проверки вектора проверки.

Что вы можете сделать, это расширить SecureRandom таким образом, чтобы он возвращал детерминированные данные. Очевидно, вы не должны использовать это при реальном развертывании:

public class FixedSecureRandom extends SecureRandom { 
    private static boolean debug = false; 
    private static final long serialVersionUID = 1L; 
    public FixedSecureRandom() { } 
    private int nextBytesIndex = 0; 

    private byte[] nextBytesValues = null; 

    public void setBytes(byte[] values) { 
     this.nextBytesValues = values; 
    } 

    public void nextBytes(byte[] b) { 
     if (nextBytesValues==null) { 
      super.nextBytes(b); 
     } else if (nextBytesValues.length==0) { 
      super.nextBytes(b); 
     } else { 
      for (int i=0; i<b.length; i++) { 
       b[i] = nextBytesValues[nextBytesIndex]; 
       nextBytesIndex = (nextBytesIndex + 1) % nextBytesValues.length; 
      } 
     } 
    } 
} 

Фуэ. Итак, теперь у вас есть класс SecureRandom, который возвращает вам некоторое количество известных байтов, а затем возвращается к реальному SecureRandom после этого. Я скажу это снова (извините за крик) - НЕ ИСПОЛЬЗУЙТЕ ЭТО В ПРОИЗВОДСТВЕ!

Далее вам нужно будет использовать реализацию ECDSA, которая позволит вам указать свой собственный SecureRandom. Вы можете использовать BouncyCastle's ECDSASigner для этой цели. Кроме того, вы собираетесь предоставить свой собственный загрузочный файл FixedSecureRandom, так что, когда он называет secureRandom.getBytes(), он получает байты, которые вы хотите. Это позволяет вам управлять эфемерным ключом, соответствующим тому, который указан в тестовых векторах.Возможно, вам придется массировать фактические байты (например, добавить нулевое предварительное заполнение) в соответствии с запросом ECDSASigner.

ECPrivateKeyParameters ecPriv = ...; // this is the user's EC private key (not ephemeral) 

FixedSecureRandom fsr_k = new FixedSecureRandom(); 
fsr_k.setBytes(tempKeyK); 

ECDSASigner signer = new ECDSASigner(); 
ParametersWithRandom ecdsaprivrand = new ParametersWithRandom(ecPriv, fsr_k); 
signer.init(true, ecdsaprivrand); 

Обратите внимание, что в Британской Колумбии ECDSASigner реализует только сигнатура часть ЕС, а не хеширования. Вы все еще должны будете сделать свое собственное хеширование (предполагается, что входные данные в data):

Digest md = new SHA256Digest() 
md.reset(); 
md.update(data, 0, data.length); 
byte[] hash = new byte[md.getDigestSize()]; 
md.doFinal(hash, 0); 

, прежде чем создать ECDSA подпись:

BigInteger[] sig = signer.generateSignature(hash); 

Наконец, этот BigInteger[] (должно быть длиной = = 2) - значения (r, s). Вам понадобится ASN.1 DER-encode it, который должен дать вам дроидов байт, которые вы ищете.

+1

успеха! Спасибо за быстрый и очень подробный ответ. Как только я прочитал это, это имело смысл, и все это теперь работает на меня. Огромное спасибо за вашу помощь :-) – PinkyNoBrain

+0

P.S Для тех, кто еще смотрит на это. Я изначально сделал ошибку новобранец, выполнив fsr_k.setBytes (новый BigInteger («9E56F509196784D963D1C0A401510EE7ADA3DCC5DEE04B154BF61AF1D5A6DECE», 16) .toByteArray()) и почти заплакал. При этом дополнительный 0 байт добавляется к массиву байтов, и поэтому вы все равно получаете неправильное значение K :-( – PinkyNoBrain

+0

@PinkyNoBrain Вам нужна функция под названием I2OS (целое число с октетной строкой). Этот метод принимает * два * входа , целочисленная и (ключевая) длина. Он возвращает беззнаковое большое кодирование конца целого числа с байтами '00', добавленными до размера ключа. Теперь у вас есть неподписанная часть, убедитесь, что у вас есть и прописная часть. –

1

вот мой полный тест следующее решение tsechin, используя BouncyCastle но торчащий к хорошему старому JCA API:

byte[] input = { 0x61, 0x62, 0x63 }; 

    //Create the static private key W from the Test Vector 
    ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("secp256r1"); 
    org.bouncycastle.jce.spec.ECPrivateKeySpec privateKeySpec = new org.bouncycastle.jce.spec.ECPrivateKeySpec(new BigInteger("DC51D3866A15BACDE33D96F992FCA99DA7E6EF0934E7097559C27F1614C88A7F", 16), parameterSpec); 
    KeyFactory kf = KeyFactory.getInstance("EC"); 
    ECPrivateKey spriv = (ECPrivateKey) kf.generatePrivate(privateKeySpec); 

    //Perfrom ECDSA signature of the data with SHA-256 as the hash algorithm 
    Signature dsa = Signature.getInstance("SHA256withECDSA", "BC"); 
    FixedSecureRandom random = new FixedSecureRandom(); 
    random.setBytes(Hex.decode("9E56F509196784D963D1C0A401510EE7ADA3DCC5DEE04B154BF61AF1D5A6DECE")); 
    dsa.initSign(spriv, random); 
    dsa.update(input); 
    byte[] output = dsa.sign(); 

    // compare the signature with the expected reference values 
    ASN1Sequence sequence = ASN1Sequence.getInstance(output); 
    DERInteger r = (DERInteger) sequence.getObjectAt(0); 
    DERInteger s = (DERInteger) sequence.getObjectAt(1); 
    Assert.assertEquals(r.getValue(), new BigInteger("CB28E0999B9C7715FD0A80D8E47A77079716CBBF917DD72E97566EA1C066957C", 16)); 
    Assert.assertEquals(s.getValue(), new BigInteger("86FA3BB4E26CAD5BF90B7F81899256CE7594BB1EA0C89212748BFF3B3D5B0315", 16)); 
+0

help, me. Я выполняю 'ECPrivateKey spriv = (ECPrivateKey) kf.generatePrivate (privateKeySpec);' делает приложение игнорирует остальную часть кода – doraemon