BouncyCastle를 활용한 SM2, SM3, SM4 암호화 구현

본 문서는 BouncyCastle 라이브러리를 사용하여 중국 국가 암호 표준인 SM2(공개키 암호), SM3(해시 함수), SM4(대칭 키 암호) 알고리즘을 Java 환경에서 구현하는 방법을 설명합니다. openEuler 또는 Ubuntu 환경을 권장하며, Windows 사용은 비권장입니다.

의존성 설정

BouncyCastle의 최신 JAR 파일을 다운로드해야 합니다. 다음 링크에서 버전 1.73을 가져옵니다:

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();
    }
}

태그: BouncyCastle SM2 SM3 SM4 Java 암호화

6월 5일 17:37에 게시됨