Monday, June 13, 2022

Using ECDSA in Android to Sign and Verify Messages



 

In this post we will review how to sign and verify messages in Android using ECDSA. This is an asymmetric encryption mechanism that is based on elliptic curve cryptography.


First, lets generate a private key. We will use a class with an empty constructor for the generation.


import org.bouncycastle.crypto.signers.StandardDSAEncoding;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;


public class EncryptionKey {

static {
Security.removeProvider("BC");//first remove default os provider
// this is working even it appears in the studio as compile error
BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider();
Security.insertProviderAt(bouncyCastleProvider, 1);//add new provider
}

private final KeyPair keyPair;
private final String address;

public EncryptionKey() {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
keyGen.initialize(new ECGenParameterSpec("secp256k1"), new SecureRandom());
this.keyPair = keyGen.generateKeyPair();
this.address = this.calculateAddress();
} catch (Exception e) {
throw new BouncerException(e);
}
}



The key can be exported as hex string:


public String exportKeyPair() {
byte[] bytesPrivateKey = this.keyPair.getPrivate().getEncoded();
byte[] bytesPublicKey = this.keyPair.getPublic().getEncoded();
String hexPrivateKey = BytesUtils.bytesToHexString(bytesPrivateKey);
String hexPublicKey = BytesUtils.bytesToHexString(bytesPublicKey);
return hexPrivateKey + " " + hexPublicKey;
}



And the public key can be exported as X and Y factors:



private String calculateAddress() {
ECPublicKey ecPublicKey = (ECPublicKey) this.keyPair.getPublic();
return BytesUtils.bigIntsToBase64(
"X", "Y",
ecPublicKey.getW().getAffineX(),
ecPublicKey.getW().getAffineY()
);
}



We can import from the exported hex string using another constructor:



public EncryptionKey(String exportedKeyPair) {
try {
String[] hexKeys = exportedKeyPair.split(" ");

String hexPrivateKey = hexKeys[0];
String hexPublicKey = hexKeys[1];

byte[] bytesPrivateKey = BytesUtils.hexStringToByteArray(hexPrivateKey);
byte[] bytesPublicKey = BytesUtils.hexStringToByteArray(hexPublicKey);

PKCS8EncodedKeySpec specPrivateKey = new PKCS8EncodedKeySpec(bytesPrivateKey);
X509EncodedKeySpec specPublicKey = new X509EncodedKeySpec(bytesPublicKey);

KeyFactory factory = KeyFactory.getInstance("ECDSA");

PrivateKey privateKey = factory.generatePrivate(specPrivateKey);
PublicKey publicKey = factory.generatePublic(specPublicKey);

this.keyPair = new KeyPair(publicKey, privateKey);
this.address = this.calculateAddress();
} catch (Exception e) {
throw new BouncerException(e);
}
}



Now the last thing to handle is the sign, and verify methods:



public String sign(String text) {
try {
Signature ecdsa = Signature.getInstance("SHA256withECDSA");

ecdsa.initSign(this.keyPair.getPrivate());

byte[] textBytes = text.getBytes(StandardCharsets.UTF_8);
ecdsa.update(textBytes);

byte[] signatureBytes = ecdsa.sign();
ECPublicKey publicKey = (ECPublicKey) this.keyPair.getPublic();
BigInteger order = publicKey.getParams().getOrder();
BigInteger[] bigInts = StandardDSAEncoding.INSTANCE.decode(order, signatureBytes);
BigInteger r = bigInts[0];
BigInteger s = bigInts[1];
return BytesUtils.bigIntsToBase64("r", "s", r, s);
} catch (Exception e) {
throw new BouncerException(e);
}
}


public void validate(String text, String signature) {
try {
Signature ecdsa = Signature.getInstance("SHA256withECDSA");

ecdsa.initVerify(this.keyPair.getPublic());

byte[] textBytes = text.getBytes(StandardCharsets.UTF_8);
ecdsa.update(textBytes);

BigInteger[] bigInts =BytesUtils.base64ToBigInts("r","s",signature);
BigInteger r = bigInts[0];
BigInteger s = bigInts[1];
ECPublicKey publicKey = (ECPublicKey) this.keyPair.getPublic();
BigInteger order = publicKey.getParams().getOrder();
byte[] bytes = StandardDSAEncoding.INSTANCE.encode(order, r,s);

if (!ecdsa.verify(bytes)) {
throw new BouncerException("verify failed");
}
} catch (Exception e) {
throw new BouncerException(e);update
}
}



To make this work, we need to add the depdendencies in the app/build.gradle:


dependencies {
implementation 'org.bouncycastle:bcpkix-jdk15on:1.64'
implementation 'org.bouncycastle:bcprov-jdk15on:1.64'



Final Note


This post is part of a series of posts about encryption. The full posts list is below.











No comments:

Post a Comment