11package io .opentdf .platform .sdk ;
22
3- import org .bouncycastle .asn1 .pkcs .PrivateKeyInfo ;
4- import org .bouncycastle .asn1 .x509 .SubjectPublicKeyInfo ;
5- import org .bouncycastle .cert .X509CertificateHolder ;
6- import org .bouncycastle .crypto .digests .SHA256Digest ;
7- import org .bouncycastle .crypto .generators .HKDFBytesGenerator ;
8- import org .bouncycastle .crypto .params .HKDFParameters ;
9- import org .bouncycastle .jcajce .provider .asymmetric .ec .KeyPairGeneratorSpi ;
10- import org .bouncycastle .jce .ECNamedCurveTable ;
11- import org .bouncycastle .jce .interfaces .ECPrivateKey ;
12- import org .bouncycastle .jce .interfaces .ECPublicKey ;
13- import org .bouncycastle .jce .provider .BouncyCastleProvider ;
14- import org .bouncycastle .jce .spec .ECNamedCurveParameterSpec ;
15- import org .bouncycastle .math .ec .ECPoint ;
16- import org .bouncycastle .openssl .PEMException ;
17- import org .bouncycastle .openssl .PEMParser ;
18- import org .bouncycastle .openssl .jcajce .JcaPEMKeyConverter ;
19- import org .bouncycastle .util .io .pem .*;
20- import org .bouncycastle .util .io .pem .PemReader ;
21- import org .bouncycastle .jce .spec .ECPublicKeySpec ;
22-
233import javax .crypto .KeyAgreement ;
24- import java .io .*;
25- import java .security .*;
26- import java .security .spec .*;
4+ import javax .crypto .Mac ;
5+ import javax .crypto .spec .SecretKeySpec ;
6+ import java .math .BigInteger ;
7+ import java .security .InvalidAlgorithmParameterException ;
8+ import java .security .InvalidKeyException ;
9+ import java .security .KeyPair ;
10+ import java .security .KeyPairGenerator ;
11+ import java .security .NoSuchAlgorithmException ;
12+ import java .security .Signature ;
13+ import java .security .SignatureException ;
14+ import java .security .interfaces .ECPrivateKey ;
15+ import java .security .interfaces .ECPublicKey ;
16+ import java .security .spec .ECGenParameterSpec ;
17+ import java .security .spec .ECParameterSpec ;
18+ import java .security .spec .ECPoint ;
19+ import java .util .Base64 ;
2720import java .util .Objects ;
28- // https://www.bouncycastle.org/latest_releases.html
2921
3022public class ECKeyPair {
3123
3224 private static final int SHA256_BYTES = 32 ;
33-
34- static {
35- Security .addProvider (new BouncyCastleProvider ());
36- }
25+ private static final String EC_ALGORITHM = "EC" ;
3726
3827 private final ECCurve curve ;
3928
@@ -42,8 +31,6 @@ public enum ECAlgorithm {
4231 ECDSA
4332 }
4433
45- private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider ();
46-
4734 private KeyPair keyPair ;
4835
4936 public ECKeyPair () {
@@ -52,27 +39,14 @@ public ECKeyPair() {
5239
5340 public ECKeyPair (ECCurve curve , ECAlgorithm algorithm ) {
5441 this .curve = Objects .requireNonNull (curve );
55- KeyPairGenerator generator ;
56-
42+ Objects .requireNonNull (algorithm );
5743 try {
58- // Should this just use the algorithm vs use ECDH only for ECDH and ECDSA for
59- // everything else.
60- if (algorithm == ECAlgorithm .ECDH ) {
61- generator = KeyPairGeneratorSpi .getInstance (ECAlgorithm .ECDH .name (), BOUNCY_CASTLE_PROVIDER );
62- } else {
63- generator = KeyPairGeneratorSpi .getInstance (ECAlgorithm .ECDSA .name (), BOUNCY_CASTLE_PROVIDER );
64- }
65- } catch (NoSuchAlgorithmException e ) {
44+ KeyPairGenerator generator = KeyPairGenerator .getInstance (EC_ALGORITHM );
45+ generator .initialize (new ECGenParameterSpec (this .curve .getCurveName ()));
46+ this .keyPair = generator .generateKeyPair ();
47+ } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e ) {
6648 throw new RuntimeException (e );
6749 }
68-
69- ECGenParameterSpec spec = new ECGenParameterSpec (this .curve .getCurveName ());
70- try {
71- generator .initialize (spec );
72- } catch (InvalidAlgorithmParameterException e ) {
73- throw new RuntimeException (e );
74- }
75- this .keyPair = generator .generateKeyPair ();
7650 }
7751
7852 public ECPublicKey getPublicKey () {
@@ -88,148 +62,28 @@ ECCurve getCurve() {
8862 }
8963
9064 public String publicKeyInPEMFormat () {
91- StringWriter writer = new StringWriter ();
92- PemWriter pemWriter = new PemWriter (writer );
93-
94- try {
95- pemWriter .writeObject (new PemObject ("PUBLIC KEY" , this .keyPair .getPublic ().getEncoded ()));
96- pemWriter .flush ();
97- pemWriter .close ();
98- } catch (IOException e ) {
99- throw new RuntimeException (e );
100- }
101-
102- return writer .toString ();
65+ return toPem ("PUBLIC KEY" , this .keyPair .getPublic ().getEncoded ());
10366 }
10467
10568 public String privateKeyInPEMFormat () {
106- StringWriter writer = new StringWriter ();
107- PemWriter pemWriter = new PemWriter (writer );
108-
109- try {
110- pemWriter .writeObject (new PemObject ("PRIVATE KEY" , this .keyPair .getPrivate ().getEncoded ()));
111- pemWriter .flush ();
112- pemWriter .close ();
113- } catch (IOException e ) {
114- throw new RuntimeException (e );
115- }
116-
117- return writer .toString ();
69+ return toPem ("PRIVATE KEY" , this .keyPair .getPrivate ().getEncoded ());
11870 }
11971
12072 public int keySize () {
12173 return this .keyPair .getPrivate ().getEncoded ().length * 8 ;
12274 }
12375
12476 public byte [] compressECPublickey () {
125- return ((ECPublicKey ) this .keyPair .getPublic ()).getQ ().getEncoded (true );
126- }
127-
128- public static String getPEMPublicKeyFromX509Cert (String pemInX509Format ) {
129- try {
130- PEMParser parser = new PEMParser (new StringReader (pemInX509Format ));
131- X509CertificateHolder x509CertificateHolder = (X509CertificateHolder ) parser .readObject ();
132- parser .close ();
133- SubjectPublicKeyInfo publicKeyInfo = x509CertificateHolder .getSubjectPublicKeyInfo ();
134- JcaPEMKeyConverter converter = new JcaPEMKeyConverter ().setProvider (BOUNCY_CASTLE_PROVIDER );
135- ECPublicKey publicKey = null ;
136- try {
137- publicKey = (ECPublicKey ) converter .getPublicKey (publicKeyInfo );
138- } catch (PEMException e ) {
139- throw new RuntimeException (e );
140- }
141-
142- // EC public key to pem formated.
143- StringWriter writer = new StringWriter ();
144- PemWriter pemWriter = new PemWriter (writer );
145-
146- pemWriter .writeObject (new PemObject ("PUBLIC KEY" , publicKey .getEncoded ()));
147- pemWriter .flush ();
148- pemWriter .close ();
149- return writer .toString ();
150- } catch (IOException e ) {
151- throw new RuntimeException (e );
152- }
153- }
154-
155- public static byte [] compressECPublickey (String pemECPubKey ) {
156- try {
157- KeyFactory ecKeyFac = KeyFactory .getInstance ("EC" , "BC" );
158- PemReader pemReader = new PemReader (new StringReader (pemECPubKey ));
159- PemObject pemObject = pemReader .readPemObject ();
160- PublicKey pubKey = ecKeyFac .generatePublic (new X509EncodedKeySpec (pemObject .getContent ()));
161- return ((ECPublicKey ) pubKey ).getQ ().getEncoded (true );
162- } catch (NoSuchAlgorithmException e ) {
163- throw new RuntimeException (e );
164- } catch (IOException e ) {
165- throw new RuntimeException (e );
166- } catch (InvalidKeySpecException e ) {
167- throw new RuntimeException (e );
168- } catch (NoSuchProviderException e ) {
169- throw new RuntimeException (e );
170- }
171- }
172-
173- public static String publicKeyFromECPoint (byte [] ecPoint , String curveName ) {
174- try {
175- // Create EC Public key
176- ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable .getParameterSpec (curveName );
177- ECPoint point = ecSpec .getCurve ().decodePoint (ecPoint );
178- ECPublicKeySpec publicKeySpec = new ECPublicKeySpec (point , ecSpec );
179- KeyFactory keyFactory = KeyFactory .getInstance ("ECDSA" , "BC" );
180- PublicKey publicKey = keyFactory .generatePublic (publicKeySpec );
181-
182- // EC Public keu to pem format.
183- StringWriter writer = new StringWriter ();
184- PemWriter pemWriter = new PemWriter (writer );
185- pemWriter .writeObject (new PemObject ("PUBLIC KEY" , publicKey .getEncoded ()));
186- pemWriter .flush ();
187- pemWriter .close ();
188- return writer .toString ();
189- } catch (InvalidKeySpecException e ) {
190- throw new RuntimeException (e );
191- } catch (NoSuchAlgorithmException e ) {
192- throw new RuntimeException (e );
193- } catch (NoSuchProviderException e ) {
194- throw new RuntimeException (e );
195- } catch (IOException e ) {
196- throw new RuntimeException (e );
197- }
198- }
199-
200- public static ECPublicKey publicKeyFromPem (String pemEncoding ) {
201- try {
202- PEMParser parser = new PEMParser (new StringReader (pemEncoding ));
203- SubjectPublicKeyInfo publicKeyInfo = (SubjectPublicKeyInfo ) parser .readObject ();
204- parser .close ();
205-
206- JcaPEMKeyConverter converter = new JcaPEMKeyConverter ().setProvider (BOUNCY_CASTLE_PROVIDER );
207- return (ECPublicKey ) converter .getPublicKey (publicKeyInfo );
208- } catch (IOException e ) {
209- throw new RuntimeException (e );
210- }
211- }
212-
213- public static ECPrivateKey privateKeyFromPem (String pemEncoding ) {
214- try {
215- PEMParser parser = new PEMParser (new StringReader (pemEncoding ));
216- PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo ) parser .readObject ();
217- parser .close ();
218-
219- JcaPEMKeyConverter converter = new JcaPEMKeyConverter ().setProvider (BOUNCY_CASTLE_PROVIDER );
220- return (ECPrivateKey ) converter .getPrivateKey (privateKeyInfo );
221- } catch (IOException e ) {
222- throw new RuntimeException (e );
223- }
77+ return encodeCompressedPoint ((ECPublicKey ) this .keyPair .getPublic ());
22478 }
22579
22680 public static byte [] computeECDHKey (ECPublicKey publicKey , ECPrivateKey privateKey ) {
22781 try {
228- KeyAgreement aKeyAgree = KeyAgreement .getInstance ("ECDH" , "BC" );
82+ KeyAgreement aKeyAgree = KeyAgreement .getInstance ("ECDH" );
22983 aKeyAgree .init (privateKey );
23084 aKeyAgree .doPhase (publicKey , true );
23185 return aKeyAgree .generateSecret ();
232- } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException e ) {
86+ } catch (NoSuchAlgorithmException | InvalidKeyException e ) {
23387 throw new RuntimeException (e );
23488 }
23589 }
@@ -239,46 +93,78 @@ public static byte[] computeECDHKey(ECPublicKey publicKey, ECPrivateKey privateK
23993 * that is 32 bytes (256 bits) long.
24094 */
24195 public static byte [] calculateHKDF (byte [] salt , byte [] secret ) {
242- byte [] key = new byte [SHA256_BYTES ];
243- HKDFParameters params = new HKDFParameters (secret , salt , null );
244-
245- HKDFBytesGenerator hkdf = new HKDFBytesGenerator (SHA256Digest .newInstance ());
246- hkdf .init (params );
247- hkdf .generateBytes (key , 0 , key .length );
248- return key ;
96+ try {
97+ // RFC 5869: if salt is absent, substitute a zero-filled buffer of Hash output size.
98+ byte [] effectiveSalt = (salt == null || salt .length == 0 ) ? new byte [SHA256_BYTES ] : salt ;
99+ Mac hmac = Mac .getInstance ("HmacSHA256" );
100+ hmac .init (new SecretKeySpec (effectiveSalt , "HmacSHA256" ));
101+ byte [] prk = hmac .doFinal (secret );
102+
103+ // HKDF-Expand with empty info and L = 32 (a single HMAC block).
104+ hmac .init (new SecretKeySpec (prk , "HmacSHA256" ));
105+ hmac .update ((byte ) 0x01 );
106+ return hmac .doFinal ();
107+ } catch (NoSuchAlgorithmException | InvalidKeyException e ) {
108+ throw new RuntimeException (e );
109+ }
249110 }
250111
251112 public static byte [] computeECDSASig (byte [] digest , ECPrivateKey privateKey ) {
252113 try {
253- Signature ecdsaSign = Signature .getInstance ("SHA256withECDSA" , "BC" );
114+ Signature ecdsaSign = Signature .getInstance ("SHA256withECDSA" );
254115 ecdsaSign .initSign (privateKey );
255116 ecdsaSign .update (digest );
256117 return ecdsaSign .sign ();
257- } catch (NoSuchAlgorithmException e ) {
258- throw new RuntimeException (e );
259- } catch (NoSuchProviderException e ) {
260- throw new RuntimeException (e );
261- } catch (InvalidKeyException e ) {
262- throw new RuntimeException (e );
263- } catch (SignatureException e ) {
118+ } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e ) {
264119 throw new RuntimeException (e );
265120 }
266121 }
267122
268123 public static Boolean verifyECDSAig (byte [] digest , byte [] signature , ECPublicKey publicKey ) {
269124 try {
270- Signature ecdsaVerify = Signature .getInstance ("SHA256withECDSA" , "BC" );
125+ Signature ecdsaVerify = Signature .getInstance ("SHA256withECDSA" );
271126 ecdsaVerify .initVerify (publicKey );
272127 ecdsaVerify .update (digest );
273128 return ecdsaVerify .verify (signature );
274- } catch (NoSuchAlgorithmException e ) {
275- throw new RuntimeException (e );
276- } catch (NoSuchProviderException e ) {
277- throw new RuntimeException (e );
278- } catch (InvalidKeyException e ) {
279- throw new RuntimeException (e );
280- } catch (SignatureException e ) {
129+ } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e ) {
281130 throw new RuntimeException (e );
282131 }
283132 }
284- }
133+
134+ private static String toPem (String type , byte [] der ) {
135+ String b64 = Base64 .getEncoder ().encodeToString (der );
136+ StringBuilder sb = new StringBuilder ();
137+ sb .append ("-----BEGIN " ).append (type ).append ("-----\n " );
138+ for (int i = 0 ; i < b64 .length (); i += 64 ) {
139+ sb .append (b64 , i , Math .min (i + 64 , b64 .length ())).append ('\n' );
140+ }
141+ sb .append ("-----END " ).append (type ).append ("-----\n " );
142+ return sb .toString ();
143+ }
144+
145+ private static byte [] encodeCompressedPoint (ECPublicKey publicKey ) {
146+ ECPoint w = publicKey .getW ();
147+ ECParameterSpec params = publicKey .getParams ();
148+ int size = (params .getCurve ().getField ().getFieldSize () + 7 ) / 8 ;
149+ byte [] x = toFixedLength (w .getAffineX (), size );
150+ byte [] result = new byte [size + 1 ];
151+ result [0 ] = (byte ) (w .getAffineY ().testBit (0 ) ? 0x03 : 0x02 );
152+ System .arraycopy (x , 0 , result , 1 , size );
153+ return result ;
154+ }
155+
156+ private static byte [] toFixedLength (BigInteger value , int length ) {
157+ byte [] bytes = value .toByteArray ();
158+ if (bytes .length == length ) {
159+ return bytes ;
160+ }
161+ byte [] result = new byte [length ];
162+ if (bytes .length > length ) {
163+ // BigInteger.toByteArray() may prepend a zero sign byte; strip it.
164+ System .arraycopy (bytes , bytes .length - length , result , 0 , length );
165+ } else {
166+ System .arraycopy (bytes , 0 , result , length - bytes .length , bytes .length );
167+ }
168+ return result ;
169+ }
170+ }
0 commit comments