RSS

ECDSA signature verify in kotlin and Go

How to verify ECDSA signature generated by kotlin and verify it in Go?

Introduction

Elliptic Curve Digital Signature Algorithm (ECDSA) offers a variant of the Digital Signature Algorithm (DSA) which uses elliptic curve cryptography.

ECDSA keys and signatures are shorter than in RSA for the same security level. A 256-bit ECDSA signature has the same security strength like 3072-bit RSA signature.

The ECDSA signing algorithm RFC 6979 takes as input a message msg + a private key privKey and produces as output a signature, which consists of pair of integers {r, s}.

ECDSA signatures are 2 times longer than the signer’s private key for the curve used during the signing process. For example, for 256-bit elliptic curves (like secp256k1) the ECDSA signature is 512 bits (64 bytes) and for 521-bit curves (like secp521r1) the signature is 1042 bits.

Generate signature in Java

In Android app, ECDSA is supported by default.

Generate a ECDSA key pair and sign

class EcdsaSha256 {
    companion object {
        private const val KEYPIRE_ALIAS = "MY_ECDSA_KEY_PAIR"
    }

    /**
     * sign `message` with `SHA256withECDSA`.
     *
     * @return base64 encoded ASN.1 signature, or "" if can not get key pair.
     */
    fun sign(message: String): String {
        val entry = getKeyPair() ?: return ""
        val signature = Signature.getInstance("SHA256withECDSA")

        signature.initSign(entry.privateKey)
        signature.update(message.toByteArray())

        return Base64.encodeToString(signature.sign(), Base64.NO_WRAP)
    }

    private fun generateKeyPair(): KeyPair {
        val keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
        keyPairGenerator.initialize(KeyGenParameterSpec.Builder(KEYPIRE_ALIAS,
            KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY)
            .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
            .setKeySize(256)
            .build())

        return keyPairGenerator.generateKeyPair()
    }

    private fun getKeyPair(): KeyStore.PrivateKeyEntry? {
        val keyStore = KeyStore.getInstance("AndroidKeyStore")
        keyStore.load(null)

        var res = keyStore.getEntry(KEYPIRE_ALIAS, null)
        if (res == null) {
            generateKeyPair()
            keyStore.load(null)
            res = keyStore.getEntry(KEYPIRE_ALIAS, null)
        }

        return res as? KeyStore.PrivateKeyEntry
    }
}

Sample SHA256 with ECDSA sign:

ecdsaSha256 = EcdsaSha256()
val message = "To be, or not to be, that is the question."

signature = ecdsaSha256.sign(message)
Log.d(TAG, "publicKey: $publicKey")
Log.d(TAG, "message: $message")
Log.d(TAG, "signature: $signature")

Log output:

publicKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbUTlq/bTW47fORzMskrc8+wNV3ekCX1WNKJ03XOjHzOFecniFAdDaRxluAl/osB5LH6dj8Rl4InJC/vEtFZ7dA==
message: To be, or not to be, that is the question.
signature: MEUCIQC0YJirr4yM0JEmprRJfYA/7lVUohR4qEMbiJtwzhm9RwIgS3YkgWyNuMCh6o0FQXliW6xhjEYZT/cTmqsdpNH+YB0=

The signature format is ASN.1 structure message defined in RFC 5480: Elliptic Curve Cryptography Subject Public Key Information:

ECDSA-Sig-Value ::= SEQUENCE {
  r  INTEGER,
  s  INTEGER
}

We will see how to parse it in Go in next section.

Verify Signature in Go

Go have ecdsa package implements the Elliptic Curve Digital Signature Algorithm.

To verify ECDSA signature in Go, first need load public key from string:

func loadECPublicKey2(base64PublicKey string) (*ecdsa.PublicKey, error) {
	publicKeyBytes, err := base64.StdEncoding.DecodeString(base64PublicKey)
	if err != nil {
		panic(err)
	}

	pub, err := x509.ParsePKIXPublicKey(publicKeyBytes)
	if err != nil {
		return nil, errors.New("Failed to parse ECDSA public key")
	}

	publicKey, ok := pub.(*ecdsa.PublicKey)
	if !ok {
		return nil, errors.New("Not a ECDSA public key")
	}

	return publicKey, nil
}

There are two APIs in ecdsa:

  • ecdsa.Verify() verifies the signature in r, s of hash using the public key, pub.
  • ecdsa.VerifyASN1() verifies the ASN.1 encoded signature, sig, of hash using the public key, pub.

ecdsa.Verify()

ecdsa.Verify() need decode signature to get r and s first:

type ECDSASignature struct {
	R, S *big.Int
}

// ecdsaVerify1 use `ecdsa.Verify()` to verify signature
func ecdsaVerify1(base64PublicKey string, message string, base64Signature string) bool {
	ecPublicKey, err := loadECPublicKey2(base64PublicKey)
	if err != nil {
		panic(err)
	}

	hash := sha256.Sum256([]byte(message))

	sigBytes, err := base64.StdEncoding.DecodeString(base64Signature)
	if err != nil {
		panic(err)
	}

	sig := &ECDSASignature{}

	if _, err = asn1.Unmarshal(sigBytes, sig); err != nil {
		panic(err)
	}

	return ecdsa.Verify(ecPublicKey, hash[:], sig.R, sig.S)
}

Usage example:

match := ecdsaVerify1(
  "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbUTlq/bTW47fORzMskrc8+wNV3ekCX1WNKJ03XOjHzOFecniFAdDaRxluAl/osB5LH6dj8Rl4InJC/vEtFZ7dA==",
  "To be, or not to be, that is the question.",
  "MEUCIQC0YJirr4yM0JEmprRJfYA/7lVUohR4qEMbiJtwzhm9RwIgS3YkgWyNuMCh6o0FQXliW6xhjEYZT/cTmqsdpNH+YB0=",
)

fmt.Printf("ecdsaVerify1() return %v\n", match)

ecdsa.VerifyASN1()

ecdsa.VerifyASN1() is a simple way to verify ASN.1 encoded signature, you donot need manually decode signature to get r and s:

// ecdsaVerify2 use `ecdsa.VerifyASN1()` to verify signature
func ecdsaVerify2(base64PublicKey string, message string, base64Signature string) bool {
	ecPublicKey, err := loadECPublicKey2(base64PublicKey)
	if err != nil {
		panic(err)
	}

	hash := sha256.Sum256([]byte(message))

	sigBytes, err := base64.StdEncoding.DecodeString(base64Signature)
	if err != nil {
		panic(err)
	}

	return ecdsa.VerifyASN1(ecPublicKey, hash[:], sigBytes)
}

Usage example:

match := ecdsaVerify2(
  "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbUTlq/bTW47fORzMskrc8+wNV3ekCX1WNKJ03XOjHzOFecniFAdDaRxluAl/osB5LH6dj8Rl4InJC/vEtFZ7dA==",
  "To be, or not to be, that is the question.",
  "MEUCIQC0YJirr4yM0JEmprRJfYA/7lVUohR4qEMbiJtwzhm9RwIgS3YkgWyNuMCh6o0FQXliW6xhjEYZT/cTmqsdpNH+YB0=",
)

fmt.Printf("ecdsaVerify2() return %v\n", match)

References