[英]Android Java Spongycastle ECDSA Signature to subtle.crypto Javascript
我正在從我的網站中導入一組值,這些值是使用subtle.crypto用Javascript編寫的,用於對消息進行簽名。 在QR碼中,我輸入了Javascript中的鍵的X,Y和D值,這是我復制鍵的代碼:
public static KeyPair GenerateExistingKeyPair(String d, String x, String y) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
Log.d(TAG, "GenerateExistingKeyPair: PrivateKey D: " + d);
Log.d(TAG, "GenerateExistingKeyPair: PublicKey X: " + x);
Log.d(TAG, "GenerateExistingKeyPair: PublicKey Y: " + y);
BigInteger privateD = decode(d);
BigInteger publicX = decode(x);
BigInteger publicY = decode(y);
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME);;
ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("P-256");
ECPoint Q = ecSpec.getG().multiply(privateD);
ECPrivateKeySpec privSpec = new ECPrivateKeySpec(privateD, ecSpec);
ECPublicKeySpec pubSpec = new ECPublicKeySpec(Q, ecSpec);
PrivateKey privKey = keyFactory.generatePrivate(privSpec);
PublicKey pubKey = keyFactory.generatePublic(pubSpec);
KeyPair keyPair = new KeyPair(pubKey, privKey);
Log.d(TAG, "GenerateExistingKeyPair: KeyPair: " + keyPair.getPrivate().toString());
Log.d(TAG, "GenerateExistingKeyPair: " + Hex.toHexString(privKey.getEncoded()));
return keyPair;
}
我使用“解碼”是因為這些值存儲在Javascript的Base64中。
public static BigInteger decode(String value) {
byte[] decoded = android.util.Base64.decode(value, android.util.Base64.URL_SAFE);
BigInteger bigInteger = new BigInteger(Hex.toHexString(decoded), 16);
return bigInteger;
}
現在這是輸出。
D/ECDSA:: GenerateExistingKeyPair: PrivateKey D: m-lI_bV8YoNgAgNGpccXPdNtRJ4I6k0hdMdKD7NDYlI
GenerateExistingKeyPair: PublicKey X: BadCycqeFycXoL4ONkATL7vu1ZxlF66JmrSgbE2A4eY
GenerateExistingKeyPair: PublicKey Y: obTA6W6xluIdXcqRjnvq0Nh-_IfiWKV4FWziJFxXHUo
D/ECDSA:: GenerateExistingKeyPair: KeyPair: EC Private Key [ed:66:72:8b:8c:1d:97:b9:82:0b:11:c8:1f:6e:db:aa:0e:bd:67:43]
X: 5a742c9ca9e172717a0be0e3640132fbbeed59c6517ae899ab4a06c4d80e1e6
Y: a1b4c0e96eb196e21d5dca918e7bead0d87efc87e258a578156ce2245c571d4a
據我所知,X和Y是正確的,使用Base64將它們轉換回給我的值與我收到的值完全相同。 現在,我開始討論散列消息並使用WebRTC通過JSON發送事務。
public static byte[] signTransaction(Wallet wallet, byte[] msgHash) throws Exception {
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
Signature ecdsaSign = Signature.getInstance("SHA256withECDSA", BouncyCastleProvider.PROVIDER_NAME);
ecdsaSign.initSign(wallet.getKeyPair().getPrivate());
ecdsaSign.update(msgHash);
byte[] signature = ecdsaSign.sign();
Log.d(TAG, "signTransaction: " + new BigInteger(1, signature).toString(16));
return signature;
}
這是我收到的簽名:
3045022026728f6d621689955126e52ca04e5ad7d3f5633111c32ca79979022fc48f7155022100ed94989a8f9fb6bb804ee041cb2923b6ecc17876fbc55c559c93ab9becac415f
經過一番研究,我發現Java中的ECDSA簽名是ANS1 DER編碼的,而javascript中的簽名使用的是P1363格式,僅是簽名的R和S。
因此,經過一些研究,我發現了如何從簽名中提取這些值。
public static BigInteger extractR(byte[] signature) throws Exception {
int startR = (signature[1] & 0x80) != 0 ? 3 : 2;
int lengthR = signature[startR + 1];
return new BigInteger(Arrays.copyOfRange(signature, startR + 2, startR + 2 + lengthR));
}
public static BigInteger extractS(byte[] signature) throws Exception {
int startR = (signature[1] & 0x80) != 0 ? 3 : 2;
int lengthR = signature[startR + 1];
int startS = startR + 2 + lengthR;
int lengthS = signature[startS + 1];
return new BigInteger(Arrays.copyOfRange(signature, startS + 2, startS + 2 + lengthS));
}
這給了我以下價值:
26728f6d621689955126e52ca04e5ad7d3f5633111c32ca79979022fc48f7155
ed94989a8f9fb6bb804ee041cb2923b6ecc17876fbc55c559c93ab9becac415f
在上一次嘗試中,我嘗試將這兩個字符串放在一起,並將它們發送到Javascript端,但是它無法驗證,這兩個值並排的字符大小與Javascript中生成的簽名相同,但是方法
await window.crypto.subtle.verify({name: "ECDSA", hash: {name: "SHA-256"},}, publicKey, signature, data)
在javascript中仍返回false。
我的問題是,如何使Java和Javascript之間的簽名兼容? 我可以在Javascript中將其從ASN1 DER轉換為P1363嗎? 還是可以在Java中以其他方式轉換?
任何幫助,將不勝感激...
您執行兩次哈希操作,如下所示:
ecdsaSign.update(msgHash);
而ecdsaSign
對象已經執行SHA-256哈希處理。
回答我自己的問題只是為了將其關閉,我找到了未得到JS身份驗證的原因。 我創建和復制公鑰/私鑰的所有方法都有效。 我的將ASN1 DER簽名轉換為R || S值的代碼也正在工作。
我像在瀏覽器上一樣,在Hash String上簽署交易。 事實證明,瀏覽器沒有對原始哈希字符串進行簽名,但是他在簽名之前使用了哈希字符串:
async sign(msg) {
const encoder = new TextEncoder('utf-8');
const msgBuffer = encoder.encode(msg.toString());
const signedBuffer = await ECDSA.sign(this.keys.privateKey, msgBuffer);
const signedArray = Array.from(new Uint8Array(signedBuffer));
return Encryption.byteToHexString(signedArray);
}
注意以下行:
事實證明,瀏覽器正在將哈希字符串編碼為UTF-8,並對該大小為64的字節數組進行簽名,而不是對該字符串進行簽名,該字符串將具有20個左右的字節。 因此,在瀏覽器嘗試驗證我的簽名之前,它實際上對我的哈希字符串執行了相同的操作,並將其轉換為UTF-8,這就是為什么我的簽名失敗的原因,因為我沒有在與瀏覽器嘗試驗證相同的消息上簽名。
如果我更仔細地潛入JS灣,可能可以為我節省2天的時間。
感謝Maarten Bodewes試圖幫助我,您實際上指出了我的代碼中的一些缺陷,並為我缺少向您提供的JS端代碼而感到抱歉,您可能會發現此問題並在兩天前為我提供了幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.