본문 바로가기

Applied Cryptography

ECDSA with Bouncycastle Lib.

ECDSA (Elliptic Curve Digital Signature Algorithm)

Digital Signature Algorithm은 Nist 표준이며 국내 표준 전자서명 알고리즘이다.

앞에 붙은 "EC"는 Elliptic Curve 연산을 사용하여 DSA가 구현되었다는 의미로 해석할 수 있다.

단, 분명히 ECDSA가 DSA에서 파생된 알고리즘이지만, 서로 다른 알고리즘이다.

참고로 Etheruem과 그 영향을 받은 대부분의 블록체인이 사용자 계정 확인을 위해 ECDSA 전자서명을 사용한다.

 

ECDSA with Java Lib

  • 서명키(비밀키)
    • BigInteger 난수 값.
    • PrivateKey 객체로 관리됨
  • 검증키(공개키)
    • ECPoint 값; 서명키 값으로 유도되는 ECC Point.
    • PublicKey 객체로 관리됨.
  • ECC Parameter Set
    • ECDSA 서명/검증 알고리즘에 ECC 연산이 활용됨
    • ECC 연산은 EC 파라미터에 따라 연산이 상이함
    • 따라서 특정 합의된 EC 파라미터로 ECPoint를 정의하는 것이 중요함.
  • 전자서명(공개값)
    • 2 BigInteger; (r, s)
    • Signature 객체로 관리됨.

PrivateKey, PublicKey, Signature 객체는 모두 byte[] 형으로 Export하는 getEncoded() 함수를 제공한다.

그리고 Encoding은 DER 인코딩을 사용한다. 

KeyPair 생성 - random key pair

본 포스팅의 예제는 모두 Secp256k1 parameter set을 사용함.

아직 키쌍을 보유하지 않은 User는 아래 예제를 통해 랜덤한 키쌍을 생성한다.

KeyPair.getPrivate()을 통해 PrivateKey 객체를 얻고, KeyPair.getPublic()을 통해 PublicKey 객체를 얻을 수 있다.

 

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.ECGenParameterSpec;
import java.security.GeneralSecurityException;
import java.security.Security;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

static{
	Security.insertProviderAt(new BouncyCastleProvider(), 0);
}

public static KeyPair generateRandomKeyPair() {
	try {
		ECGenParameterSpec pairParams = new ECGenParameterSpec("secp256k1");
		KeyPairGenerator pairGen = KeyPairGenerator.getInstance("EC", "BC");
		pairGen.initialize(pairParams);
		return pairGen.generateKeyPair();
	} catch (GeneralSecurityException e){
		throw new RuntimeException(e);
	}
}

KeyPair 생성 - with specific private key

이미 키를 보유한 User는 자신의 키로 PrivateKey와 PublicKey 객체를 생성한다. 개인키 값을 입력으로 genPrivateKeyFromBigInt 함수를 호출하여 PrivateKey 객체를 얻고, 그 PrivateKey 객체를 입력으로 genPublicKeyFromPrivKey 함수를 호출하여 PublicKey 객체를 얻을 수 있다.

import java.security.PrivateKey;
import java.security.KeyFactory;
import java.security.GeneralSecurityException;
import java.security.Security;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

static{
	Security.insertProviderAt(new BouncyCastleProvider(), 0);
}

public static PrivateKey genPrivateKeyFromBigInt(BigInteger D) {
	ECParameterSpec paramSpec = ECNamedCurveTable.getParameterSpec("secp256k1");
	ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(D, paramSpec);

	try {
		KeyFactory kf = KeyFactory.getInstance("EC", "BC");
		return kf.generatePrivate(privateKeySpec);
	} catch (GeneralSecurityException e) {
		throw new RuntimeException(e);
	}
}


import java.security.PublicKey;
import java.security.KeyFactory;
import java.security.GeneralSecurityException;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECPublicKeySpec;

public static PublicKey genPublicKeyFromPrivKey(PrivateKey privKey) {
	try {
		ECParameterSpec ecps = ECNamedCurveTable.getParameterSpec("secp256k1");
		BigInteger D = ((ECPrivateKey)privKey).getD();
		ECPoint PK = ecps.getG().multiply(D);
		ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(PK, ecps);
		return KeyFactory.getInstance("EC", "BC").generatePublic(publicKeySpec);
	} catch (GeneralSecurityException e) {
		throw new RuntimeException(e);
	}
}

 

Signing

임의길이 byte-Array와 PrivateKey로 전자서명을 생성한다. 생성된 Signature는 내부적으로 2개의 BigInteger 값으로 관리되며, DER 인코딩 포멧으로 표현된다.

import java.security.Signature;
import java.security.GeneralSecurityException;

public static byte[] sign(byte[] msg, PrivateKey privKey) {

	try {
		Signature ecdsaSign = Signature.getInstance("SHA256withECDSA");
		ecdsaSign.initSign(privKey);
		ecdsaSign.update(msg);
		return ecdsaSign.sign();
	} catch (GeneralSecurityException e) {
		throw new RuntimeException(e);
	}
}

hashing

Signing 알고리즘은 내부적으로 byte[] msg를 Hash한 값으로 연산된다. 따라서 개발자의 팔자에 따라 msg의 Hash를 구해야 하는 경우가 생길 수 있다. 표준 ECDSA의 경우 SHA-256 Hash 알고리즘을 사용해야하는데, 예제는 다음과 같다.

import java.security.MessageDigest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

static {
	Security.insertProviderAt(new BouncyCastleProvider(), 0);
}

public static byte[] digest(byte[] msg) {

	try {
		MessageDigest md = MessageDigest.getInstance("SHA-256", "BC");
		md.update(msg);
		return md.digest();
	} catch (GeneralSecurityException e) {
		throw new RuntimeException(e);
	}
}

 

Verification

서명시 사용했던 msg와 PublicKey로 Signature를 검증한다. 

import java.security.PublicKey;
import java.security.Signature;
import java.security.GeneralSecurityException;

public static boolean verify(byte[] signed, byte[] sig, PublicKey pubKey) {

	try {
		Signature ecdsaSign = Signature.getInstance("SHA256withECDSA");
		ecdsaSign.initVerify(pubKey);
		ecdsaSign.update(signed);
		return ecdsaSign.verify(sig);
	} catch (GeneralSecurityException e) {
		throw new RuntimeException(e);
	}
}

Convert between ECPoint and PublicKey

검증키가 ECPoint로 주어진 경우, 다음 예제 프로그램을 통해 PublicKey 객체를 생성할 수 있다.

import java.security.KeyFactory;
import java.security.Security;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.jce.ECNamedCurveTable;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

static{
	Security.insertProviderAt(new BouncyCastleProvider(), 0);
}

public static PublicKey genPublicKeyFromPoint(ECPoint point) {

	try {
		ECParameterSpec ecps = ECNamedCurveTable.getParameterSpec("secp256k1");
		ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(point, ecps);
		return KeyFactory.getInstance("EC", "BC").generatePublic(publicKeySpec);
	} catch (GeneralSecurityException e) {
		throw new RuntimeException(e);
	}
}

역으로, PublicKey 객체에서 ECPoint 값을 추출하는 방법은 다음과 같다.

import java.security.PublicKey;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.ECNamedCurveTable;

public static ECPoint publicKeyToECPoint(PublicKey pubKey) {
	ECParameterSpec ecps = ECNamedCurveTable.getParameterSpec("secp256k1");
	return ecps.getCurve().decodePoint(pubKey.getEncoded());
}