본 문서는 BouncyCastle 라이브러리를 사용하여 중국 국가 암호 표준인 SM2(공개키 암호), SM3(해시 함수), SM4(대칭 키 암호) 알고리즘을 Java 환경에서 구현하는 방법을 설명합니다. openEuler 또는 Ubuntu 환경을 권장하며, Windows 사용은 비권장입니다.
의존성 설정
BouncyCastle의 최신 JAR 파일을 다운로드해야 합니다. 다음 링크에서 버전 1.73을 가져옵니다:
- https://www.bouncycastle.org/latest_releases.html
- 필요한 JAR 파일:
bcprov-jdk15to18-1.73.jar,bcprov-ext-jdk15to18-1.73.jar
SM2 공개키 암/복호화
SM2는 타원 곡선 기반의 공개키 암호 시스템으로, 다음과 같이 구현할 수 있습니다.
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.util.encoders.Hex;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
public class GMSM2 {
private static final String PLAIN_TEXT = "testData123";
public static void main(String[] args) throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
// SM2 곡선 파라미터 설정
ECGenParameterSpec sm2Curve = new ECGenParameterSpec("sm2p256v1");
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", "BC");
keyGen.initialize(sm2Curve, new SecureRandom());
KeyPair keyPair = keyGen.generateKeyPair();
PublicKey pubKey = keyPair.getPublic();
PrivateKey privKey = keyPair.getPrivate();
GMSM2Cipher cipherUtil = new GMSM2Cipher();
String encrypted = cipherUtil.encrypt(pubKey, PLAIN_TEXT);
System.out.println("암호문 (HEX): " + encrypted);
String decrypted = cipherUtil.decrypt(privKey, encrypted);
System.out.println("복호문: " + decrypted);
}
}
class GMSM2Cipher {
public String encrypt(PublicKey publicKey, String message) {
BCECPublicKey bcPub = (BCECPublicKey) publicKey;
ECParameterSpec spec = bcPub.getParameters();
org.bouncycastle.math.ec.ECPoint g = spec.getG();
org.bouncycastle.math.ec.ECPoint q = bcPub.getQ();
ECDomainParameters domain = new ECDomainParameters(spec.getCurve(), g, spec.getN(), spec.getH());
ECPublicKeyParameters pubParams = new ECPublicKeyParameters(q, domain);
SM2Engine engine = new SM2Engine();
engine.init(true, new ParametersWithRandom(pubParams, new SecureRandom()));
try {
byte[] data = message.getBytes(StandardCharsets.UTF_8);
byte[] result = engine.processBlock(data, 0, data.length);
return Hex.toHexString(result);
} catch (Exception e) {
throw new RuntimeException("SM2 암호화 실패", e);
}
}
public String decrypt(PrivateKey privateKey, String cipherHex) {
BCECPrivateKey bcPriv = (BCECPrivateKey) privateKey;
ECParameterSpec spec = bcPriv.getParameters();
ECDomainParameters domain = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN(), spec.getH());
ECPrivateKeyParameters privParams = new ECPrivateKeyParameters(bcPriv.getD(), domain);
SM2Engine engine = new SM2Engine();
engine.init(false, privParams);
try {
byte[] cipherBytes = Hex.decode(cipherHex);
byte[] plainBytes = engine.processBlock(cipherBytes, 0, cipherBytes.length);
return new String(plainBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("SM2 복호화 실패", e);
}
}
}
SM3 해시 생성
SM3은 256비트 출력을 생성하는 암호학적 해시 함수입니다.
import org.bouncycastle.crypto.digests.SM3Digest;
public class GMSM3 {
public byte[] hash(String input) {
SM3Digest digest = new SM3Digest();
byte[] data = input.getBytes();
digest.update(data, 0, data.length);
byte[] result = new byte[digest.getDigestSize()];
digest.doFinal(result, 0);
return result;
}
}
SM4 대칭 암/복호화
SM4는 128비트 블록 및 키 크기를 가지는 대칭 암호입니다. ECB 모드 기준 예제입니다.
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
public enum SM4Mode {
ECB_NO_PADDING("SM4/ECB/NoPadding"),
CBC_PKCS5("SM4/CBC/PKCS5Padding");
private final String transformation;
SM4Mode(String transformation) {
this.transformation = transformation;
}
public String getTransformation() {
return transformation;
}
}
class GMSM4 {
private final Cipher cipher;
public GMSM4() throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
this.cipher = Cipher.getInstance(SM4Mode.ECB_NO_PADDING.getTransformation(), "BC");
}
public byte[] generateKey() throws Exception {
KeyGenerator kg = KeyGenerator.getInstance("SM4", "BC");
kg.init(128, new SecureRandom());
return kg.generateKey().getEncoded();
}
public byte[] encrypt(byte[] plaintext, byte[] key) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(key, "SM4");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(plaintext);
}
public byte[] decrypt(byte[] ciphertext, byte[] key) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(key, "SM4");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return cipher.doFinal(ciphertext);
}
}
통합 테스트 예제
public class GMSTestSuite {
public static void main(String[] args) throws Exception {
// SM3 해시
GMSM3 sm3 = new GMSM3();
byte[] digest = sm3.hash("hello world");
System.out.println("SM3 해시: " + bytesToHex(digest));
// SM4 키 및 암호화
GMSM4 sm4 = new GMSM4();
byte[] key = sm4.generateKey();
byte[] encrypted = sm4.encrypt(digest, key);
byte[] decrypted = sm4.decrypt(encrypted, key);
System.out.println("SM4 복호화 성공: " + java.util.Arrays.equals(digest, decrypted));
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}