Elliptic Curve Digital Signature Algorithm (ECDSA) used in Kotlin
Cyber security has become very important in a company. It consists in defending servers, computers, mobile devices from malicious attacks. There are several encryption algorithms that can be used to protect an organization’s sensitive data, and it is up to the company to choose which one fits best.
Elliptic Curve Digital Signing Algorithm (ECDSA) refers to generating a digital signature, starting from mathematical equations regarding the elliptic curve.
ECDSA explained
ECDSA is a Digital Signature Algorithm that generates a digital signature of data (file) in order to allow you to verify its authenticity without a security breach. It uses keys derived from elliptic curve cryptography (ECC). A digital signature is a mathematical technique used to validate the authenticity and integrity of a message, software or digital document.
The ECDSA certificate is a public key certificate, also known as a digital certificate, and represents an electronic document used to demonstrate the ownership of a public key. In the ECDSA certificate, the public key and the signing keys are derived from elliptic curve cryptography.
The signature contains two parts named R and S. In order to verify that the signature, the public key is needed, which represents the point on the curve that was generated using the private key. The private key is generated as a random integer in the range [0…n-1]. The public key pubKey is a point on the elliptic curve, calculated by the EC point multiplication: pubKey = privKey * G (the private key, multiplied by the generator point G). A cryptographic hash function is applied to the original message to produce a message digest. In Figure 1is presented the signature generation and the signature verification.
Figure 1. ECDSA signature generation and the signature verification
The ECDSA signing algorithm takes as input the following:
- message msg
- a private key privKey
The output Is represented by a signature, which consists of a pair of integers {r, s}. The ECDSA signing algorithm is based on the ElGamal encryption algorithms and implies the following:
- Calculate the message hash, using a cryptographic hash function like SHA-256: h = hash(msg)
- Generate securely a random number k in the range [1..n-1]
- Calculate the random point R = k * G and take its x-coordinate: r = R.x
- Calculate the signature proof: signature {r, s}.
The ECDSA signature verification algorithm functions as:
- Calculate the message hash, with the same cryptographic hash function used during the signing: h = hash(msg)
- Calculate the modular inverse of the signature proof:
- Recover the random point used during the signing: R’ = (h * s1) * G + (r * s1) * pubKey
- Take from R’ its x-coordinate: r’ = R’.x
- Calculate the signature validation result by comparing whether r’ == r.
The verification flow is shown in Figure 2. If r’ == r, then the signature is valid.
Figure 2. The signature verification flow
If the file was signed correctly using the private key, it will result in the other part of the signature (R). You cannot create a signature using only the public key.
Basically, you use a private key to generate R and S, and if the result of the mathematical equation using the public key and S is R, then the signature verification passes.
Generating private and public keys
Before implementing the digital signing process, we first need to create a private and public key pair, which will further be used in the remainder of this article.
To generate a private and public key pair, for MacOs, use the following commands:
# Generate an Elliptic Curve parameter file.
$ openssl ecparam -name prime256v1 -out prime256v1_param.pem
# Generate an ECDSA Private key.
$ openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem
# Generate an ECDSA Public key.
$ openssl ec -in private-key.pem -pubout -out public-key.pem
# Convert private key in pks8 format
$ openssl pkcs8 -topk8 -in private-key.pem -inform pem -out pkcs8.pem -outform pem -nocrypt
# Produce signature.
$ openssl dgst -sha256 -sign prime256v1-key.pem ~/payload.json > signature.der
The conversion from PKCS1 to PKCS8 is mandatory, because PKCS8 is now the standard for handling private keys for all algorithms (more information here).
Practical example using Kotlin
Finally, some code! For this example, we will be using Kotlin. We will also need the private and public key pair from Section 2. Let’s start by creating a Kotlin extension function file, named SigningExtensionFunctions.kt, that will contain methods for initializing private and public keys, generating a signature and verifying it. The cryptographic hash functions set used is “SHA256”.
Given the private key.pem, generated from the command line, create a method to initialize the private key. We have to remove all spaces, “BEGIN PRIVATE KEY”, and “END PRIVATE KEY”, as shown below.
After initializing the private key, the next step is to create a method to initialize the public key, given an existing public key .pem file, generated from the command line, in chapter two.
Assuming that we have initialized the private and public keys, we will sign the data/file/document, with the private key.
What comes next?! Verify the authenticity of the signature!
In order to better understand the use of the methods from SigningFunctions.kt (initialization of private and public keys, signing and verifying the signature), let’s write a unit test.
Firstly, we test the signature validation, using the correct public key:
@Test
fun `given a valid private and public key pair, when signing the file, then the signature validation will return true`() {
val privateKey = initializePrivateKey(PRIVATE_KEY_PEM)
val publicKey = initializePublicKey(PUBLIC_KEY_PEM)
val dataToSign = TEST_STRING.toByteArray(Charsets.UTF_8)
val signature = signDocument(privateKey, dataToSign)
assert(verifySignature(publicKey, dataToSign, signature))
}
We validate the signature, using an incorrect public key:
@Test
fun `given an valid private key and a invalid public key, when signing the file, then the signature validation will fail`() {
val privateKey = initializePrivateKey(PRIVATE_KEY_PEM)
val publicKeyPEM = INVALID_PUBLIC_KEY
val publicKey = initializePublicKey(publicKeyPEM)
val dataToSign = TEST_STRING.toByteArray(Charsets.UTF_8)
val signature = signDocument(privateKey, dataToSign)
Assert.assertFalse(verifySignature(publicKey, dataToSign, signature))
}
Another case we can test is the one that throws an InvalidKeySpecException:
@Test(expected = InvalidKeySpecException::class)
fun `given an invalid private key and a valid public key, when signing the file, then the signing process will fail with InvalidKeySpecException`() {
val privateKeyPEM = """ - - -BEGIN PRIVATE KEY - - -
MIGkAgEBBDD2MFRv6BpJU6/zDI2yBfVbe0oeU1nFAoYMedDGtcdwHyWNJSeiYRBA
pVNzMxPSBLWgBwYFK4EEACKhZANiAAQBttEp/qUGnDlmL+o6KZnVs+RoBnEBEGho
PxSUu1Xfj77QQqfuqHOCRzWXseQA1aZB/h6VQEiFovugtG1G3HaMxxrqLLxb10g2
BMaRcAfZyeqc3O0Ui8XXb1esn0gOrCu=
- - -END PRIVATE KEY - - -""".trimIndent()
val privateKey = initializePrivateKey(privateKeyPEM)
val publicKey = initializePublicKey(PUBLIC_KEY_PEM)
val dataToSign = TEST_STRING.toByteArray(Charsets.UTF_8)
val signature = signDocument(privateKey, dataToSign)
verify(verifySignature(publicKey, dataToSign, signature))
}
}
The code can be found here.
Further reading
For more information about the Elliptic Curve algorithm, read these articles:
- https://www.maximintegrated.com/en/design/technical-documents/tutorials/5/5767.html
- https://www.instructables.com/Understanding-how-ECDSA-protects-your-data/
- https://techdifferences.com/difference-between-symmetric-and-asymmetric-encryption.html
- https://cryptobook.nakov.com/digital-signatures/ecdsa-sign-verify-messages
- https://www.instructables.com/Understanding-how-ECDSA-protects-your-data/
- https://asecuritysite.com/encryption/ecdsa3
Happy learning!