From 48f657e04cc11ff71710f5bf36095af3adb83780 Mon Sep 17 00:00:00 2001 From: jaewohong Date: Wed, 17 Dec 2025 09:31:45 +0900 Subject: [PATCH] =?UTF-8?q?PKI=20=EC=95=94=ED=98=B8=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exchange/crypto/ECDHESA256Cipher.java | 179 ++++++++++++++---- .../encrypt/exchange/crypto/NPKILoader.java | 60 ++++++ .../exchange/crypto/RSAOAEP256Cipher.java | 54 ++++++ 3 files changed, 259 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/kjbank/encrypt/exchange/crypto/NPKILoader.java create mode 100644 src/main/java/com/kjbank/encrypt/exchange/crypto/RSAOAEP256Cipher.java diff --git a/src/main/java/com/kjbank/encrypt/exchange/crypto/ECDHESA256Cipher.java b/src/main/java/com/kjbank/encrypt/exchange/crypto/ECDHESA256Cipher.java index a382e17..6bd0530 100644 --- a/src/main/java/com/kjbank/encrypt/exchange/crypto/ECDHESA256Cipher.java +++ b/src/main/java/com/kjbank/encrypt/exchange/crypto/ECDHESA256Cipher.java @@ -7,14 +7,32 @@ import com.nimbusds.jose.jwk.gen.*; import java.io.FileInputStream; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.cert.Certificate; import java.security.interfaces.ECPublicKey; import java.security.KeyStore; import java.security.PrivateKey; +//공개키 관련 (RSA) +import java.security.interfaces.RSAPublicKey; +import java.security.PublicKey; + +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.KeyUse; + +import java.io.FileNotFoundException; +import java.security.cert.CertificateException; +import java.io.IOException; +import java.util.Collection; +import java.security.Security; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + public class ECDHESA256Cipher { private ECKey myECKey; private ECKey receiverPublicJWK; + private RSAKey receiverPublicRSAKey; + private PublicKey pubKey; + private JWEObject jweObject; // 생성자: 수신자 키 쌍을 준비 public ECDHESA256Cipher() throws Exception { @@ -28,70 +46,163 @@ public class ECDHESA256Cipher { //생성자: 내 키쌍 로드용 생성자 (복호화할 때 필요) - public ECDHESA256Cipher(String myKeystorePath, String storePass, String keyAlias, String keyPass) throws Exception { + // 생성자: 키스토어에서 RSA 개인키/인증서 로드 (복호화용) + public ECDHESA256Cipher(String keystorePath, String storePass, String keyAlias, String keyPass) throws Exception { KeyStore ks = KeyStore.getInstance("PKCS12"); - ks.load(new FileInputStream(myKeystorePath), storePass.toCharArray()); + try (FileInputStream fis = new FileInputStream(keystorePath)) { + ks.load(fis, storePass.toCharArray()); - PrivateKey myPrivateKey = (PrivateKey) ks.getKey(keyAlias, keyPass.toCharArray()); - X509Certificate myCert = (X509Certificate) ks.getCertificate(keyAlias); - ECPublicKey myPublicKey = (ECPublicKey) myCert.getPublicKey(); + PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias, keyPass.toCharArray()); + X509Certificate cert = (X509Certificate) ks.getCertificate(keyAlias); - this.myECKey = new ECKey.Builder(Curve.P_256, myPublicKey) - .privateKey(myPrivateKey) - .keyUse(KeyUse.ENCRYPTION) - //.keyID("my-key") - .keyID(myCert.getSerialNumber().toString()) // 인증서 시리얼번호를 keyID로 사용 - .build(); + if (privateKey == null || cert == null) { + throw new IllegalStateException("키스토어에서 개인키 또는 인증서를 로드하지 못했습니다."); + } + + this.pubKey = cert.getPublicKey(); + System.out.println(pubKey.getAlgorithm()); // "RSA" 또는 "EC" + + if ("RSA".equals(pubKey.getAlgorithm())) { + RSAPublicKey rsaPub = (RSAPublicKey) cert.getPublicKey(); + this.receiverPublicRSAKey = new RSAKey.Builder(rsaPub) + .privateKey(privateKey) // ★ 개인키 포함 + .keyUse(KeyUse.ENCRYPTION) + .keyID(cert.getSerialNumber().toString()) + .build(); + } else if ("EC".equals(pubKey.getAlgorithm())) { + ECPublicKey ecPub = (ECPublicKey) cert.getPublicKey(); + // EC 복호화를 위해서는 EC 개인키도 필요합니다. + this.myECKey = new ECKey.Builder(Curve.P_256, ecPub) + .privateKey(privateKey) + .keyUse(KeyUse.ENCRYPTION) + .keyID(cert.getSerialNumber().toString()) + .build(); + } else { + throw new IllegalStateException("지원하지 않는 공개키 알고리즘: " + pubKey.getAlgorithm()); + } + } } + // 생성자: 상대방 인증서에서 공개키 로드 public ECDHESA256Cipher(String certPath) throws Exception { + //0. 개인키 새성 + //String certPath = "C:/imsi/mytestcert/signCert.der"; + String priPath = "C:/imsi/mytestcert/signPri.key"; + char[] password = "looking100!".toCharArray(); + Security.addProvider(new BouncyCastleProvider()); + PrivateKey myPrivateKey1 = NPKILoader.loadPrivateKey(priPath, password, pubKey.getAlgorithm()); + // 1. 인증서 로드 CertificateFactory cf = CertificateFactory.getInstance("X.509"); try (FileInputStream fis = new FileInputStream(certPath)) { - X509Certificate cert = (X509Certificate) cf.generateCertificate(fis); + X509Certificate cert = null; + //인증서 묶음인 경우(PKCS#7 경우) + Collection certs = cf.generateCertificates(fis); + for (Certificate c : certs) { + if (c instanceof X509Certificate) { + cert = (X509Certificate) c; + System.out.println("Subject: " + cert.getSubjectDN()); + } + } + + if (cert == null) { + throw new IllegalStateException("인증서를 찾을 수 없습니다."); + } - // 2. 공개키 추출 - ECPublicKey publicKey = (ECPublicKey) cert.getPublicKey(); + // 공개키 저장 및 알고리즘 확인 + this.pubKey = cert.getPublicKey(); + System.out.println(pubKey.getAlgorithm()); // "RSA" 또는 "EC" - // 3. Nimbus ECKey로 변환 - this.receiverPublicJWK = new ECKey.Builder(Curve.P_256, publicKey) - .keyUse(KeyUse.ENCRYPTION) - //.keyID("receiver-key") - .keyID(cert.getSerialNumber().toString()) // 인증서 시리얼번호를 keyID로 사용 - .build(); + KeyStore ks = KeyStore.getInstance("PKCS12"); + + if ("RSA".equals(pubKey.getAlgorithm())) { + //PrivateKey myPrivateKey = (PrivateKey) ks.getKey(keyAlias, keyPass.toCharArray()); + //PrivateKey myPrivateKey = (PrivateKey) ks.getKey("mykey", "key1234".toCharArray()); + PrivateKey myPrivateKey = NPKILoader.loadPrivateKey(priPath, password, pubKey.getAlgorithm()); + System.out.println("PrivateKey Alg: " + myPrivateKey.getAlgorithm()); + + RSAPublicKey publicKey = (RSAPublicKey) cert.getPublicKey(); + this.receiverPublicRSAKey = new RSAKey.Builder(publicKey) + .privateKey(myPrivateKey) //개인키 포함 + .keyUse(KeyUse.ENCRYPTION) + .keyID(cert.getSerialNumber().toString()) + .build(); + } else if ("EC".equals(pubKey.getAlgorithm())) { + ECPublicKey ecPub = (ECPublicKey) cert.getPublicKey(); + this.receiverPublicJWK = new ECKey.Builder(Curve.P_256, ecPub) + .keyUse(KeyUse.ENCRYPTION) + .keyID(cert.getSerialNumber().toString()) + .build(); + } else { + throw new IllegalStateException("지원하지 않는 공개키 알고리즘: " + pubKey.getAlgorithm()); + } + + } catch (FileNotFoundException e) { + System.err.println("인증서 파일을 찾을 수 없습니다: " + e.getMessage()); + } catch (CertificateException e) { + System.err.println("인증서 파싱 오류: " + e.getMessage()); + } catch (IOException e) { + System.err.println("파일 읽기 오류: " + e.getMessage()); + } catch (ClassCastException e) { + System.err.println("X509Certificate 형변환 오류: " + e.getMessage()); } } // 암호화 메서드 public String encrypt(String jsonPayload) throws Exception { - if (receiverPublicJWK == null) { + if (receiverPublicJWK == null && receiverPublicRSAKey == null ) { throw new IllegalStateException("상대방 공개키가 초기화되지 않았습니다."); } - - JWEHeader header = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES_A256KW, EncryptionMethod.A256GCM) - .contentType("application/json") - .keyID(receiverPublicJWK.getKeyID()) - .build(); + + if (receiverPublicJWK != null && KeyType.EC.equals(receiverPublicJWK.getKeyType())) { + JWEHeader header = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES_A256KW, EncryptionMethod.A256GCM) + .contentType("application/json") + .keyID(receiverPublicJWK.getKeyID()) + .build(); - JWEObject jweObject = new JWEObject(header, new Payload(jsonPayload)); + this.jweObject = new JWEObject(header, new Payload(jsonPayload)); - JWEEncrypter encrypter = new ECDHEncrypter(receiverPublicJWK); - jweObject.encrypt(encrypter); + JWEEncrypter encrypter = new ECDHEncrypter(receiverPublicJWK); + jweObject.encrypt(encrypter); + + } else if (receiverPublicRSAKey != null && "RSA".equals(receiverPublicRSAKey.getKeyType().getValue())) { + JWEHeader header = new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM) + .contentType("application/json") + .keyID(receiverPublicRSAKey.getKeyID()) + .build(); + this.jweObject = new JWEObject(header, new Payload(jsonPayload)); + + JWEEncrypter encrypter = new RSAEncrypter(receiverPublicRSAKey); + jweObject.encrypt(encrypter); + } return jweObject.serialize(); + } // 복호화 메서드(상대방이 나한테 보낸것) public String decrypt(String jweString) throws Exception { - if (myECKey == null) { - throw new IllegalStateException("내 개인키가 초기화되지 않았습니다."); + if (myECKey == null && receiverPublicRSAKey == null) { + throw new IllegalStateException("내 개인키 또는 RSA 키가 초기화되지 않았습니다."); } + JWEObject decryptedJWE = JWEObject.parse(jweString); - JWEDecrypter decrypter = new ECDHDecrypter(myECKey); - decryptedJWE.decrypt(decrypter); - return decryptedJWE.getPayload().toString(); + if (myECKey != null) { + // EC 키로 복호화 + JWEDecrypter decrypter = new ECDHDecrypter(myECKey); + decryptedJWE.decrypt(decrypter); + + } else if (receiverPublicRSAKey != null && receiverPublicRSAKey.isPrivate()) { + // RSA 키로 복호화 + JWEDecrypter decrypter = new RSADecrypter(receiverPublicRSAKey.toPrivateKey()); + decryptedJWE.decrypt(decrypter); + } else { + throw new IllegalStateException("복호화에 필요한 키가 준비되지 않았습니다. (EC 개인키 또는 RSA 개인키)"); + } + + return decryptedJWE.getPayload().toString(); } // 상대방 공개키를 외부에 제공할 수 있도록 getter diff --git a/src/main/java/com/kjbank/encrypt/exchange/crypto/NPKILoader.java b/src/main/java/com/kjbank/encrypt/exchange/crypto/NPKILoader.java new file mode 100644 index 0000000..aa84d9e --- /dev/null +++ b/src/main/java/com/kjbank/encrypt/exchange/crypto/NPKILoader.java @@ -0,0 +1,60 @@ +package com.kjbank.encrypt.exchange.crypto; + +import java.io.FileInputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.*; +import java.security.cert.*; +import java.security.spec.PKCS8EncodedKeySpec; +import javax.crypto.*; +import javax.crypto.spec.PBEKeySpec; + +public class NPKILoader { + + // 1) Provider 등록 (예: KISA SEED JCE, KSign 등) + static { + // 예시: Security.addProvider(new com.kisa.seed.KISASeedJCE()); + // 또는 Security.addProvider(new com.ksign.security.provider.KsignProvider()); + // 실제 사용하는 라이브러리 명으로 교체 + } + + public static X509Certificate loadCert(String certDerPath) throws Exception { + try (FileInputStream fis = new FileInputStream(certDerPath)) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + return (X509Certificate) cf.generateCertificate(fis); + } + } + + public static PrivateKey loadPrivateKey(String priDerPath, char[] password, String keyAlg) throws Exception { + // 암호화된 PKCS#8 (DER) 읽기 + byte[] enc = Files.readAllBytes(Paths.get(priDerPath)); + EncryptedPrivateKeyInfo encInfo = new EncryptedPrivateKeyInfo(enc); + + // 알고리즘 확인 (OID → 알고리즘 이름 매핑 필요) + String algName = encInfo.getAlgName(); // 일부 Provider는 빈 문자열을 반환할 수 있음 + AlgorithmParameters params = encInfo.getAlgParameters(); + + // 국내 OID 매핑: 1.2.410.200004.1.15 → PBEWithSHA1AndSEED (Provider에 따라 이름 다를 수 있음) + if (algName == null || algName.isEmpty()) { + String oid = encInfo.getAlgParameters().getAlgorithm(); // 일부 Provider는 여기서도 OID를 줍니다 + // 필요 시 강제 설정 + algName = "PBEWithSHA1AndSEED"; // 사용 중인 Provider 문서의 이름으로 맞추세요 + } + + // PBE 키 생성 + SecretKeyFactory skf = SecretKeyFactory.getInstance(algName); + PBEKeySpec pbeKeySpec = new PBEKeySpec(password); + SecretKey pbeKey = skf.generateSecret(pbeKeySpec); + + // 복호화 + Cipher cipher = Cipher.getInstance(algName); + cipher.init(Cipher.DECRYPT_MODE, pbeKey, params); + byte[] pkcs8 = cipher.doFinal(encInfo.getEncryptedData()); + + // PKCS#8 → PrivateKey + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8); + KeyFactory kf = KeyFactory.getInstance(keyAlg); // "RSA" 또는 "EC" + return kf.generatePrivate(keySpec); + } +} + diff --git a/src/main/java/com/kjbank/encrypt/exchange/crypto/RSAOAEP256Cipher.java b/src/main/java/com/kjbank/encrypt/exchange/crypto/RSAOAEP256Cipher.java new file mode 100644 index 0000000..10d1790 --- /dev/null +++ b/src/main/java/com/kjbank/encrypt/exchange/crypto/RSAOAEP256Cipher.java @@ -0,0 +1,54 @@ +package com.kjbank.encrypt.exchange.crypto; + +import com.nimbusds.jose.*; +import com.nimbusds.jose.crypto.*; +import com.nimbusds.jose.util.StandardCharset; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import com.nimbusds.jose.crypto.RSAEncrypter; +import java.security.interfaces.RSAPublicKey; + +public class RSAOAEP256Cipher { + + // 암호화: 상대방 공개 인증서(RSA)로 암호화 + public static String encrypt(byte[] plaintext, X509Certificate recipientCert, String kidOptional) throws Exception { + // 인증서에서 RSA 공개키 추출 + if (!"RSA".equalsIgnoreCase(recipientCert.getPublicKey().getAlgorithm())) { + throw new IllegalArgumentException("Recipient certificate public key is not RSA"); + } + RSAPublicKey recipientPublicKey = (RSAPublicKey) recipientCert.getPublicKey(); + + JWEHeader header = new JWEHeader.Builder( + JWEAlgorithm.RSA_OAEP_256, + EncryptionMethod.A256GCM + ) + .contentType("application/octet-stream") + .keyID(kidOptional) + .build(); + + JWEObject jweObject = new JWEObject(header, new Payload(plaintext)); + JWEEncrypter encrypter = new RSAEncrypter(recipientPublicKey); + jweObject.encrypt(encrypter); + + return jweObject.serialize(); + } + + // 복호화: 내 RSA 개인키로 복호화 + public static byte[] decrypt(String jweCompact, PrivateKey myPrivateKey) throws Exception { + JWEObject jweObject = JWEObject.parse(jweCompact); + JWEDecrypter decrypter = new RSADecrypter(myPrivateKey); + jweObject.decrypt(decrypter); + return jweObject.getPayload().toBytes(); + } + + // 편의 함수 + public static String encryptText(String text, X509Certificate cert, String kidOptional) throws Exception { + return encrypt(text.getBytes(StandardCharset.UTF_8), cert, kidOptional); + } + + public static String decryptToText(String jweCompact, PrivateKey myPrivateKey) throws Exception { + return new String(decrypt(jweCompact, myPrivateKey), StandardCharset.UTF_8); + } +} \ No newline at end of file