我需要使用BouncyCastle加密提供程序使用Java验证ECDSA签名。到目前为止,BouncyCastle未能验证签名。
签名在 Atmel AT88CK590加密认证模块中创建,公钥可以从模块中获取。以下是C / C ++格式的公钥,长度为64个八位位组:
uint8_t pubKey[] = {
// X coordinate of the elliptic curve.
0xc1, 0x71, 0xCB, 0xED, 0x65, 0x71, 0x82, 0x2E, 0x8F, 0x8A, 0x43, 0x8D, 0x72, 0x56, 0xD1, 0xC8,
0x86, 0x3C, 0xD0, 0xBC, 0x7F, 0xCC, 0xE3, 0x6D, 0xE7, 0xB7, 0x17, 0xED, 0x29, 0xC8, 0x38, 0xCB,
// Y coordinate of the elliptic curve.
0x80, 0xCD, 0xBE, 0x0F, 0x1D, 0x5C, 0xC5, 0x46, 0x99, 0x24, 0x8F, 0x6E, 0x0A, 0xEA, 0x1F, 0x7A,
0x43, 0xBA, 0x2B, 0x03, 0x80, 0x90, 0xE9, 0x25, 0xB2, 0xD0, 0xE6, 0x48, 0x93, 0x91, 0x64, 0x83
};
原始邮件,Base64编码中的签名和公钥:
// Raw message to sign
private static final String tokenStr = "12345678901234567890123456789012";
// Base64 encoded
private static final String pubKeyStr = "wXHL7WVxgi6PikONclbRyIY80Lx/zONt57cX7SnIOMuAzb4PHVzFRpkkj24K6h96
Q7orA4CQ6SWy0OZIk5Fkgw==";
// Base64 encoded
private static final String signatureStr = "XF2WossFTA82ndYFGEH0FPqAldkFQLGd/Bv/Qh8UYip7sXUvCUnFgi1YXjN3WxLn
IwSo3OaHLCOzGAtIis0b3A==";
要转换公钥,我使用以下内容:
private static PublicKey getPublicKeyFromBytes(byte[] pubKey, String ecSpec, String provider)
throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(ecSpec);
KeyFactory kf = KeyFactory.getInstance(ECDSA_CRYPTO, provider);
ECNamedCurveSpec params = new ECNamedCurveSpec(ecSpec, spec.getCurve(), spec.getG(), spec.getN());
ECPoint pubPoint = ECPointUtil.decodePoint(params.getCurve(), pubKey);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(pubPoint, params);
PublicKey publicKey = kf.generatePublic(pubKeySpec);
return publicKey;
}
要将签名转换为DER格式,我使用以下内容:
private static byte[] toDERSignature(byte[] tokenSignature) throws IOException {
byte[] r = Arrays.copyOfRange(tokenSignature, 0, tokenSignature.length / 2);
byte[] s = Arrays.copyOfRange(tokenSignature, tokenSignature.length / 2, tokenSignature.length);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DEROutputStream derOutputStream = new DEROutputStream(byteArrayOutputStream);
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(new ASN1Integer(new BigInteger(1, r)));
v.add(new ASN1Integer(new BigInteger(1, s)));
derOutputStream.writeObject(new DERSequence(v));
byte[] derSignature = byteArrayOutputStream.toByteArray();
return derSignature;
}
以下是验证签名的代码:
Security.addProvider(new BouncyCastleProvider());
byte[] tokenBytes = tokenStr.getBytes("UTF-8");
String urlDecodePubKeyStr = pubKeyStr.replace(newlineHtml, "");
byte[] pubKeyBytes = DatatypeConverter.parseBase64Binary(urlDecodePubKeyStr);
String urlDecodeSignatureStr = signatureStr.replace(newlineHtml, "");
byte[] signBytes = DatatypeConverter.parseBase64Binary(urlDecodeSignatureStr);
byte[] derSignature = toDERSignature(signBytes);
ByteBuffer bb = ByteBuffer.allocate(pubKeyBytes.length + 1);
bb.put((byte)4);
bb.put(pubKeyBytes);
PublicKey ecPublicKey = getPublicKeyFromBytes(bb.array(), "prime256v1", "BC");
System.out.println("\nSignature: " + Hex.toHexString(signBytes).toUpperCase());
System.out.println("DER Signature: " + Hex.toHexString(derSignature).toUpperCase());
System.out.println(ecPublicKey.toString());
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
signature.initVerify(ecPublicKey);
signature.update(tokenBytes);
boolean result = signature.verify(derSignature);
System.out.println("BC Signature Valid: " + result);
输出:
Signature: 5C5D96A2CB054C0F369DD6051841F414FA8095D90540B19DFC1BFF421F14622A7BB1752F0949C5822D585E33775B12E72304A8DCE6872C23B3180B488ACD1BDC
DER Signature: 304402205C5D96A2CB054C0F369DD6051841F414FA8095D90540B19DFC1BFF421F14622A02207BB1752F0949C5822D585E33775B12E72304A8DCE6872C23B3180B488ACD1BDC
EC Public Key
X: c171cbed6571822e8f8a438d7256d1c8863cd0bc7fcce36de7b717ed29c838cb
Y: 80cdbe0f1d5cc54699248f6e0aea1f7a43ba2b038090e925b2d0e64893916483
BC Signature Valid: false
有没有人遇到过同样的问题?我在这里想念的是什么?
答案 0 :(得分:2)
该签名值未在散列上计算,这是标准和通常,但直接在数据上。之一:
Atmel没有哈希所以你必须先哈希并提供哈希输出才能签名
Atmel 应该哈希但你没有设置一些必要的选项或其他东西
你实际上想要一个没有哈希的非常规“原始”签名。请注意,这会将数据限制为32或31个字节。如果要签名的值可能受到攻击者的影响并且用于签名的随机生成器很弱,我认为这会产生一种攻击,可以恢复你的私钥(虽然我没有完成它)。即使使用if,我也会避免这种风险。
无论如何,您可以使用方案 NoneWithECDSA
验证您拥有的价值。
此外,在测试中,我发现您可能会或可能不想要的代码中的一些可能的改进。首先,你不需要准编码然后解码点,假设您有固定格式的X,Y,你可以使用它们。其次,您需要对签名进行DER编码,但可以更简单地完成。为清晰起见,这是我的版本,包含两个更改,单个线性块:
public static void main (String[] args) throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
// test data; real data would come from outside
byte[] dataBytes = "12345678901234567890123456789012".getBytes("UTF-8");
String sigString = "XF2WossFTA82ndYFGEH0FPqAldkFQLGd/Bv/Qh8UYip7sXUvCUnFgi1YXjN3WxLn
IwSo3OaHLCOzGAtIis0b3A==";
String pubkeyString = "wXHL7WVxgi6PikONclbRyIY80Lx/zONt57cX7SnIOMuAzb4PHVzFRpkkj24K6h96
Q7orA4CQ6SWy0OZIk5Fkgw==";
String ecSpec="prime256v1"; int size=32; // bytes for x,y in pubkey also r,s in sig
byte[] pubkeyBytes = DatatypeConverter.parseBase64Binary(pubkeyString.replaceAll("
","") );
KeyFactory kf = KeyFactory.getInstance ("ECDSA", "BC");
ECNamedCurveParameterSpec cspec = ECNamedCurveTable.getParameterSpec(ecSpec);
BigInteger x = new BigInteger(1, Arrays.copyOfRange(pubkeyBytes,0,size));
BigInteger y = new BigInteger(1, Arrays.copyOfRange(pubkeyBytes,size,size*2));
ECPublicKeySpec kspec = new ECPublicKeySpec (cspec.getCurve().createPoint(x, y), cspec);
PublicKey k = kf.generatePublic(kspec);
byte[] sigBytes = DatatypeConverter.parseBase64Binary(sigString.replaceAll("
","") );
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(sigBytes,0,size))));
v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(sigBytes,size,size*2))));
byte[] sigDer = new DERSequence(v).getEncoded();
Signature sig = Signature.getInstance("NoneWithECDSA", "BC"); // NOTE None instead of a hash
sig.initVerify (k); sig.update (dataBytes);
System.out.println ("verify="+sig.verify(sigDer));
}
PS:你将公钥一半评为X [Y] coordinate of the elliptic curve
。它们是 on 曲线上的坐标,即公钥;曲线本身没有坐标。