|
2 | 2 |
|
3 | 3 | import java.security.KeyPair; |
4 | 4 | import java.security.KeyPairGenerator; |
5 | | -import java.security.interfaces.EdECPrivateKey; |
6 | | -import java.security.interfaces.EdECPublicKey; |
| 5 | +import java.security.Security; |
7 | 6 | import java.util.Arrays; |
8 | 7 | import org.bitcoinj.core.Base58; |
9 | | -import org.bouncycastle.jcajce.interfaces.XDHPrivateKey; |
10 | | -import org.bouncycastle.jcajce.interfaces.XDHPublicKey; |
| 8 | +import org.bouncycastle.jce.provider.BouncyCastleProvider; |
11 | 9 |
|
12 | 10 | public class Ed25519KeyGenerator { |
13 | 11 |
|
| 12 | + static { |
| 13 | + // BouncyCastle Provider 등록 (X25519 지원을 위해 필요) |
| 14 | + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { |
| 15 | + Security.addProvider(new BouncyCastleProvider()); |
| 16 | + } |
| 17 | + } |
| 18 | + |
14 | 19 | public static KeyMaterial generate() throws Exception { |
15 | 20 |
|
16 | | - KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519"); |
17 | | - KeyPair kp = kpg.generateKeyPair(); |
| 21 | + // Ed25519 키 쌍 생성 |
| 22 | + KeyPairGenerator ed25519Kpg = KeyPairGenerator.getInstance("Ed25519"); |
| 23 | + KeyPair ed25519Kp = ed25519Kpg.generateKeyPair(); |
| 24 | + |
| 25 | + // Ed25519 Public Key 처리 |
| 26 | + byte[] ed25519PubSpki = ed25519Kp.getPublic().getEncoded(); |
| 27 | + byte[] ed25519RawPub = |
| 28 | + Arrays.copyOfRange( |
| 29 | + ed25519PubSpki, ed25519PubSpki.length - 32, ed25519PubSpki.length); |
| 30 | + |
| 31 | + // Ed25519 Private Key 처리 |
| 32 | + byte[] ed25519PrivPkcs8 = ed25519Kp.getPrivate().getEncoded(); |
| 33 | + byte[] ed25519RawPriv = |
| 34 | + Arrays.copyOfRange( |
| 35 | + ed25519PrivPkcs8, ed25519PrivPkcs8.length - 32, ed25519PrivPkcs8.length); |
18 | 36 |
|
19 | | - byte[] pubSpki = kp.getPublic().getEncoded(); |
20 | | - byte[] rawPub = Arrays.copyOfRange(pubSpki, pubSpki.length - 32, pubSpki.length); |
| 37 | + // Ed25519 Public Key multicodec encoding (0xED 0x01) |
| 38 | + byte[] ed25519PubPrefixed = new byte[ed25519RawPub.length + 2]; |
| 39 | + ed25519PubPrefixed[0] = (byte) 0xED; |
| 40 | + ed25519PubPrefixed[1] = 0x01; |
| 41 | + System.arraycopy(ed25519RawPub, 0, ed25519PubPrefixed, 2, ed25519RawPub.length); |
21 | 42 |
|
22 | | - byte[] privPkcs8 = kp.getPrivate().getEncoded(); |
23 | | - byte[] rawPriv = Arrays.copyOfRange(privPkcs8, privPkcs8.length - 32, privPkcs8.length); |
| 43 | + String ed25519PublicKeyBase58 = "z" + Base58.encode(ed25519PubPrefixed); |
| 44 | + String ed25519PrivateKeyBase58 = Base58.encode(ed25519RawPriv); |
24 | 45 |
|
25 | | - byte[] prefixed = new byte[rawPub.length + 2]; |
26 | | - prefixed[0] = (byte) 0xED; |
27 | | - prefixed[1] = 0x01; |
28 | | - System.arraycopy(rawPub, 0, prefixed, 2, rawPub.length); |
| 46 | + // X25519 키 쌍 생성 (별도의 독립적인 키 쌍) |
| 47 | + KeyPairGenerator x25519Kpg = KeyPairGenerator.getInstance("X25519", "BC"); |
| 48 | + KeyPair x25519Kp = x25519Kpg.generateKeyPair(); |
29 | 49 |
|
30 | | - String publicKeyBase58 = "z" + Base58.encode(prefixed); |
31 | | - String privateKeyBase58 = Base58.encode(rawPriv); |
| 50 | + // X25519 Public Key 처리 |
| 51 | + byte[] x25519PubEncoded = x25519Kp.getPublic().getEncoded(); |
| 52 | + byte[] x25519RawPub = extractX25519PublicKey(x25519PubEncoded); |
32 | 53 |
|
33 | | - KeyPairGenerator xKpg = KeyPairGenerator.getInstance("X25519", "BC"); |
34 | | - KeyPair xKp = xKpg.generateKeyPair(); |
| 54 | + // X25519 Private Key 처리 |
| 55 | + byte[] x25519PrivEncoded = x25519Kp.getPrivate().getEncoded(); |
| 56 | + byte[] x25519RawPriv = extractX25519PrivateKey(x25519PrivEncoded); |
35 | 57 |
|
36 | | - byte[] xPubSpki = xKp.getPublic().getEncoded(); |
37 | | - byte[] xRawPub = Arrays.copyOfRange(xPubSpki, xPubSpki.length - 32, xPubSpki.length); |
| 58 | + // X25519 Public Key multicodec encoding (0xEC 0x01) |
| 59 | + byte[] x25519PubPrefixed = new byte[x25519RawPub.length + 2]; |
| 60 | + x25519PubPrefixed[0] = (byte) 0xEC; |
| 61 | + x25519PubPrefixed[1] = 0x01; |
| 62 | + System.arraycopy(x25519RawPub, 0, x25519PubPrefixed, 2, x25519RawPub.length); |
38 | 63 |
|
39 | | - /* multicodec: 0xEC 0x01 = X25519 public key */ |
40 | | - byte[] xPrefixed = new byte[xRawPub.length + 2]; |
41 | | - xPrefixed[0] = (byte) 0xEC; |
42 | | - xPrefixed[1] = 0x01; |
43 | | - System.arraycopy(xRawPub, 0, xPrefixed, 2, xRawPub.length); |
| 64 | + String x25519PublicKeyMb58 = "z" + Base58.encode(x25519PubPrefixed); |
44 | 65 |
|
45 | | - String agreementKeyMb58 = "z" + Base58.encode(xPrefixed); |
46 | | - byte[] xPrivSpki = xKp.getPrivate().getEncoded(); |
47 | | - // PKCS#8 또는 SPKI 형식으로 인코딩된 값이 넘어오므로, 끝 32바이트가 실제 raw private |
48 | | - byte[] xRawPriv = Arrays.copyOfRange(xPrivSpki, xPrivSpki.length - 32, xPrivSpki.length); |
| 66 | + // X25519 Private Key multicodec encoding (0x82 0x26) |
| 67 | + byte[] x25519PrivPrefixed = new byte[x25519RawPriv.length + 2]; |
| 68 | + x25519PrivPrefixed[0] = (byte) 0x82; |
| 69 | + x25519PrivPrefixed[1] = 0x26; |
| 70 | + System.arraycopy(x25519RawPriv, 0, x25519PrivPrefixed, 2, x25519RawPriv.length); |
49 | 71 |
|
50 | | - // multicodec 형식 붙이기 (0xEC 0x01 = X25519 private multicodec) |
51 | | - byte[] xPrivPrefixed = new byte[xRawPriv.length + 2]; |
52 | | - xPrivPrefixed[0] = (byte) 0xEC; |
53 | | - xPrivPrefixed[1] = 0x01; |
54 | | - System.arraycopy(xRawPriv, 0, xPrivPrefixed, 2, xRawPriv.length); |
| 72 | + String x25519PrivateKeyMb58 = "z" + Base58.encode(x25519PrivPrefixed); |
55 | 73 |
|
56 | | - // 최종적으로 multibase58 (z-prefixed) 문자열 |
57 | | - String x25519PrivateMb58 = "z" + Base58.encode(xPrivPrefixed); |
| 74 | + // 디버깅 출력 |
| 75 | + System.out.println("=== Ed25519 Keys ==="); |
| 76 | + System.out.println("Ed25519 Public (hex): " + bytesToHex(ed25519RawPub)); |
| 77 | + System.out.println("Ed25519 Private (hex): " + bytesToHex(ed25519RawPriv)); |
| 78 | + |
| 79 | + System.out.println("\n=== X25519 Keys ==="); |
| 80 | + System.out.println("X25519 Public (hex): " + bytesToHex(x25519RawPub)); |
| 81 | + System.out.println("X25519 Private (hex): " + bytesToHex(x25519RawPriv)); |
| 82 | + |
| 83 | + System.out.println("\n=== Encoded Keys ==="); |
| 84 | + System.out.println("Ed25519 Public MB58: " + ed25519PublicKeyBase58); |
| 85 | + System.out.println("Ed25519 Private B58: " + ed25519PrivateKeyBase58); |
| 86 | + System.out.println("X25519 Public MB58: " + x25519PublicKeyMb58); |
| 87 | + System.out.println("X25519 Private MB58: " + x25519PrivateKeyMb58); |
58 | 88 |
|
59 | | - /* 반환 객체에 추가로 포함 */ |
60 | 89 | return new KeyMaterial( |
61 | | - rawPub, |
62 | | - rawPriv, |
63 | | - publicKeyBase58, |
64 | | - privateKeyBase58, |
65 | | - agreementKeyMb58, // NEW: X25519 public multibase |
66 | | - (EdECPublicKey) kp.getPublic(), |
67 | | - (EdECPrivateKey) kp.getPrivate(), |
68 | | - (XDHPublicKey) xKp.getPublic(), |
69 | | - (XDHPrivateKey) xKp.getPrivate(), |
70 | | - x25519PrivateMb58); |
| 90 | + ed25519RawPub, // rawPub (Ed25519) |
| 91 | + ed25519RawPriv, // rawPriv (Ed25519) |
| 92 | + ed25519PublicKeyBase58, // signingKeyMb58 (Ed25519 public) |
| 93 | + ed25519PrivateKeyBase58, // signingPrivBase58 (Ed25519 private) |
| 94 | + x25519PublicKeyMb58, // agreementKeyMb58 (X25519 public) |
| 95 | + x25519PrivateKeyMb58 // x25519PrivateMb58 (X25519 private) |
| 96 | + ); |
| 97 | + } |
| 98 | + |
| 99 | + /** |
| 100 | + * X25519 Public Key에서 원시 32바이트 추출 SPKI 형식: 30 2A 30 05 06 03 2B 65 6E 03 21 00 [32바이트 public |
| 101 | + * key] |
| 102 | + */ |
| 103 | + private static byte[] extractX25519PublicKey(byte[] encoded) { |
| 104 | + // SPKI 형식에서 마지막 32바이트가 실제 public key |
| 105 | + if (encoded.length < 32) { |
| 106 | + throw new IllegalArgumentException("Invalid X25519 public key encoding"); |
| 107 | + } |
| 108 | + return Arrays.copyOfRange(encoded, encoded.length - 32, encoded.length); |
| 109 | + } |
| 110 | + |
| 111 | + /** X25519 Private Key에서 원시 32바이트 추출 PKCS#8 형식에서 실제 private key 데이터 추출 */ |
| 112 | + private static byte[] extractX25519PrivateKey(byte[] encoded) { |
| 113 | + // PKCS#8 형식 파싱 |
| 114 | + // 30 2E 02 01 00 30 05 06 03 2B 65 6E 04 22 04 20 [32바이트 private key] |
| 115 | + |
| 116 | + if (encoded.length < 32) { |
| 117 | + throw new IllegalArgumentException("Invalid X25519 private key encoding"); |
| 118 | + } |
| 119 | + |
| 120 | + // PKCS#8에서 private key는 OCTET STRING 내부에 있음 |
| 121 | + // 일반적으로 마지막 32바이트가 실제 private key |
| 122 | + // 하지만 더 정확한 파싱을 위해 OCTET STRING을 찾음 |
| 123 | + |
| 124 | + for (int i = 0; i < encoded.length - 34; i++) { |
| 125 | + if (encoded[i] == 0x04 |
| 126 | + && encoded[i + 1] == 0x22 |
| 127 | + && encoded[i + 2] == 0x04 |
| 128 | + && encoded[i + 3] == 0x20) { |
| 129 | + // 0x04 0x22 = OCTET STRING (34 bytes) |
| 130 | + // 0x04 0x20 = OCTET STRING (32 bytes) - actual private key |
| 131 | + return Arrays.copyOfRange(encoded, i + 4, i + 36); |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + // fallback: 마지막 32바이트 사용 |
| 136 | + return Arrays.copyOfRange(encoded, encoded.length - 32, encoded.length); |
| 137 | + } |
| 138 | + |
| 139 | + /** 바이트 배열을 hex 문자열로 변환 */ |
| 140 | + private static String bytesToHex(byte[] bytes) { |
| 141 | + StringBuilder result = new StringBuilder(); |
| 142 | + for (byte b : bytes) { |
| 143 | + result.append(String.format("%02x", b)); |
| 144 | + } |
| 145 | + return result.toString(); |
71 | 146 | } |
72 | 147 | } |
0 commit comments