ECDSA signature Java vs Go

♀尐吖头ヾ 提交于 2019-12-10 10:46:34

问题


I am trying to learn some Go and blockchains.. Starting with ECDSA signatures. Trying to figure out how to test if I had a correctly working Go implementation of ECDSA signatures, I figured I would try to create a similar version in Java and compare the results to see if I can get them to match.

So Java attempt:

public static void main(String[] args) throws Exception {
    //the keys below are previously generated with "generateKey();" and base64 encoded
    generateKey();
    String privStr = "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAQ7bMVIcWr9NpSD3hPkns5C0qET87UvyY5WI6UML2p0Q==";
    String pubStr = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAES8VdACZT/9u1NmaiQk0KIjEXxiaxms74nu/ps6bP0OvYMIlTdIWWU2s35LEKsNJH9u5QM2ocX53BPjwbsENXJw==";
    PrivateKey privateKey = base64ToPrivateKey(privStr);
    PublicKey publicKey = base64ToPublicKey(pubStr);
    String str = "This is string to sign";
    byte[] signature = signMsg(str, privateKey);
    boolean ok = verifySignature(publicKey, str, signature);
    System.out.println("signature ok:" + ok);
    String privHex = getPrivateKeyAsHex(privateKey);
}

public static byte[] signMsg(String msg, PrivateKey priv) throws Exception {
    Signature ecdsa = Signature.getInstance("SHA1withECDSA");

    ecdsa.initSign(priv);

    byte[] strByte = msg.getBytes("UTF-8");
    ecdsa.update(strByte);

    byte[] realSig = ecdsa.sign();
    //the printed signature from here is what is used in the Go version (hex string)
    System.out.println("Signature: " + new BigInteger(1, realSig).toString(16));
    return realSig;
}

//https://stackoverflow.com/questions/30175149/error-when-verifying-ecdsa-signature-in-java-with-bouncycastle

private static boolean verifySignature(PublicKey pubKey, String msg, byte[] signature) throws Exception {
    byte[] message = msg.getBytes("UTF-8");
    Signature ecdsa = Signature.getInstance("SHA1withECDSA");
    ecdsa.initVerify(pubKey);
    ecdsa.update(message);
    return ecdsa.verify(signature);
}

public static String generateKey() throws Exception {
    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
    SecureRandom random = SecureRandom.getInstance("SHA1PRNG");

    keyGen.initialize(256, random); //256 bit key size

    KeyPair pair = keyGen.generateKeyPair();
    PrivateKey priv = pair.getPrivate();
    ECPrivateKey ePriv = (ECPrivateKey) priv;
    PublicKey pub = pair.getPublic();

    //https://stackoverflow.com/questions/5355466/converting-secret-key-into-a-string-and-vice-versa
    String encodedPrivateKey = Base64.getEncoder().encodeToString(priv.getEncoded());
    byte[] pubEncoded = pub.getEncoded();
    String encodedPublicKey = Base64.getEncoder().encodeToString(pubEncoded);
    System.out.println(encodedPrivateKey);
    System.out.println(encodedPublicKey);
    return encodedPrivateKey;
}

public static PrivateKey base64ToPrivateKey(String encodedKey) throws Exception {
    byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
    return bytesToPrivateKey(decodedKey);
}

public static PrivateKey bytesToPrivateKey(byte[] pkcs8key) throws GeneralSecurityException {
    PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(pkcs8key);
    KeyFactory factory = KeyFactory.getInstance("EC");
    PrivateKey privateKey = factory.generatePrivate(spec);
    return privateKey;
}

public static PublicKey base64ToPublicKey(String encodedKey) throws Exception {
    byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
    return bytesToPublicKey(decodedKey);
}

public static PublicKey bytesToPublicKey(byte[] x509key) throws GeneralSecurityException {
    X509EncodedKeySpec spec = new X509EncodedKeySpec(x509key);
    KeyFactory factory = KeyFactory.getInstance("EC");
    PublicKey publicKey = factory.generatePublic(spec);
    return publicKey;
}

//https://stackoverflow.com/questions/40552688/generating-a-ecdsa-private-key-in-bouncy-castle-returns-a-public-key
private static String getPrivateKeyAsHex(PrivateKey privateKey) {
    ECPrivateKey ecPrivateKey = (ECPrivateKey) privateKey;
    byte[] privateKeyBytes = ecPrivateKey.getS().toByteArray();
    System.out.println("S:"+ecPrivateKey.getS());

    String hex = bytesToHex(privateKeyBytes);

    System.out.println("Private key bytes: " + Arrays.toString(privateKeyBytes));
    System.out.println("Private key hex: " + hex);

    return hex;
}

private final static char[] hexArray = "0123456789ABCDEF".toCharArray();

public static String bytesToHex(byte[] bytes) {
    char[] hexChars = new char[bytes.length * 2];
    for (int j = 0 ; j < bytes.length ; j++) {
        int v = bytes[j] & 0xFF;
        hexChars[j * 2] = hexArray[v >>> 4];
        hexChars[j * 2 + 1] = hexArray[v & 0x0F];
    }
    return new String(hexChars);
}

Doing the signing and verification in Java works just fine. Easy to configure of course, since they are all the same libs, parameters, and all.

To verify the same signature in Go, I tried:

func TestSigning(t *testing.T) {
    privKey := hexToPrivateKey("10EDB31521C5ABF4DA520F784F927B390B4A844FCED4BF2639588E9430BDA9D1")
    pubKey := privKey.Public()
    sig := "3045022071f06054f450f808aa53294d34f76afd288a23749628cc58add828e8b8f2b742022100f82dcb51cc63b29f4f8b0b838c6546be228ba11a7c23dc102c6d9dcba11a8ff2"
    sigHex, _ := hex.DecodeString(sig)
    ePubKey := pubKey.(*ecdsa.PublicKey)
    ok := verifyMySig(ePubKey, "This is string to sign", sigHex)
    println(ok)
}

func verifyMySig(pub *ecdsa.PublicKey, msg string, sig []byte) bool {
    r := new(big.Int).SetBytes(sig[:len(sig)/2])
    s := new(big.Int).SetBytes(sig[len(sig)/2:])
    return ecdsa.Verify(pub, []byte(msg), r, s)
}

func hexToPrivateKey(hexStr string)  *ecdsa.PrivateKey {
    bytes, _ := hex.DecodeString(hexStr)

    k := new(big.Int)
    k.SetBytes(bytes)
    println("k:")
    fmt.Println(k.String())

    priv := new(ecdsa.PrivateKey)
    curve := elliptic.P256()
    priv.PublicKey.Curve = curve
    priv.D = k
    priv.PublicKey.X, priv.PublicKey.Y = curve.ScalarBaseMult(k.Bytes())

    return priv
}

Initially, I tried to just export the Private key in Java as a base64 encoded string, and import that into Go. But I could not figure out how to get Go to load the key in the format Java stores if (X509EncodedKeySpec). So instead, I tried this way to copy the big integer of the private key only, and generate the public key from that. If I get that to work, then try to copy just the public key..

Anyway, the Go code fails to verify the signature. It is always false. Also, I cannot figure out where to put the SHA function in Go from "SHA1withECDSA" part.

I am sure I am missing some basic concepts here. How to do this properly?


回答1:


Managed to get this to work. So just to document it for myself and anyone interested..

As pointed by in comments, the signature from Java is in ASN1 format. Found a nice description of the format here: https://crypto.stackexchange.com/questions/1795/how-can-i-convert-a-der-ecdsa-signature-to-asn-1.

I also found some good examples on how to do SHAxx with ECDSA in Go at https://github.com/gtank/cryptopasta (sign.go and sign_test.go). Just need to run the relevant SHA function before the ECDSA code.

Found example code for building the public keys from parameters in Go at http://codrspace.com/supcik/golang-jwt-ecdsa/.

I paste the relevant code below, if someone finds an issue, please let me know..

Relevant Java code:

public static PublicKey bytesToPublicKey(byte[] x509key) throws GeneralSecurityException {
    X509EncodedKeySpec spec = new X509EncodedKeySpec(x509key);
    KeyFactory factory = KeyFactory.getInstance("EC");
    ECPublicKey publicKey = (ECPublicKey) factory.generatePublic(spec);
    //We should be able to use these X and Y in Go to build the public key
    BigInteger x = publicKey.getW().getAffineX();
    BigInteger y = publicKey.getW().getAffineY();
    System.out.println(publicKey.toString());
    return publicKey;
}

//we can either use the Java standard signature ANS1 format output, or just take the R and S parameters from it, and pass those to Go
//https://stackoverflow.com/questions/48783809/ecdsa-sign-with-bouncycastle-and-verify-with-crypto
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));
}

public static byte[] signMsg(String msg, PrivateKey priv) throws Exception {
    Signature ecdsa = Signature.getInstance("SHA1withECDSA");

    ecdsa.initSign(priv);

    byte[] strByte = msg.getBytes("UTF-8");
    ecdsa.update(strByte);

    byte[] realSig = ecdsa.sign();

    //this is the R and S we could also pass as the signature
    System.out.println("R: "+extractR(realSig));
    System.out.println("S: "+extractS(realSig));

    return realSig;
}

Relevant Go code:

func verifyMySig(pub *ecdsa.PublicKey, msg string, sig []byte) bool {
    //https://github.com/gtank/cryptopasta
    digest := sha1.Sum([]byte(msg))

    var esig ecdsaSignature
    asn1.Unmarshal(sig, &esig)
    //above is ASN1 decoding from the Java format. Alternatively, we can just transfer R and S parameters and set those
    //  esig.R.SetString("89498588918986623250776516710529930937349633484023489594523498325650057801271", 0)
    //  esig.S.SetString("67852785826834317523806560409094108489491289922250506276160316152060290646810", 0)
    fmt.Printf("R: %d , S: %d", esig.R, esig.S)
    println()
    return ecdsa.Verify(pub, digest[:], esig.R, esig.S)
}

func hexToPrivateKey(hexStr string)  *ecdsa.PrivateKey {
    bytes, err := hex.DecodeString(hexStr)
    print(err)

    k := new(big.Int)
    k.SetBytes(bytes)
    println("k:")
    fmt.Println(k.String())

    priv := new(ecdsa.PrivateKey)
    curve := elliptic.P256()
    priv.PublicKey.Curve = curve
    priv.D = k
    priv.PublicKey.X, priv.PublicKey.Y = curve.ScalarBaseMult(k.Bytes())
    //we can check these against the Java implementation to see if it matches to know key was transferred OK
    fmt.Printf("X: %d, Y: %d", priv.PublicKey.X, priv.PublicKey.Y)
    println()

    return priv
}


来源:https://stackoverflow.com/questions/49825455/ecdsa-signature-java-vs-go

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!