1+ /*
2+ * jPOS Project [http://jpos.org]
3+ * Copyright (C) 2000-2026 jPOS Software SRL
4+ *
5+ * This program is free software: you can redistribute it and/or modify
6+ * it under the terms of the GNU Affero General Public License as
7+ * published by the Free Software Foundation, either version 3 of the
8+ * License, or (at your option) any later version.
9+ *
10+ * This program is distributed in the hope that it will be useful,
11+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+ * GNU Affero General Public License for more details.
14+ *
15+ * You should have received a copy of the GNU Affero General Public License
16+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
17+ */
18+
19+ package org .jpos .core ;
20+
21+ import org .jpos .security .SystemSeed ;
22+
23+ import javax .crypto .Cipher ;
24+ import javax .crypto .KeyGenerator ;
25+ import javax .crypto .SecretKey ;
26+ import javax .crypto .spec .GCMParameterSpec ;
27+ import java .security .SecureRandom ;
28+ import java .util .Base64 ;
29+ import java .util .concurrent .ConcurrentHashMap ;
30+ import java .util .concurrent .ConcurrentMap ;
31+
32+ /**
33+ * Manages encryption keys derived from SystemSeed.
34+ *
35+ * <p>
36+ * This class:
37+ * <ul>
38+ * <li>Derives a consistent key from SystemSeed (32 bytes = 256 bits)</li>
39+ * <li>Supports multiple named keys for different encryption contexts</li>
40+ * <li>Provides methods to encrypt/decrypt data using these keys</li>
41+ * </ul>
42+ */
43+ public class SystemKeyManager {
44+ private static final String DEFAULT_KEY_NAME = "default" ;
45+ private static final String DEFAULT_ENV_VAR = "JPOS_ENCRYPTION_KEY" ;
46+ private static final int KEY_SIZE_BITS = 256 ;
47+
48+ private static final SystemKeyManager instance ;
49+ private static final ConcurrentMap <String , SecretKey > keys = new ConcurrentHashMap <>();
50+
51+ static {
52+ try {
53+ instance = new SystemKeyManager ();
54+ } catch (Exception e ) {
55+ throw new RuntimeException ("Failed to initialize SystemKeyManager" , e );
56+ }
57+ }
58+
59+ private SystemKeyManager () {
60+ }
61+
62+ /**
63+ * Returns the singleton SystemKeyManager instance.
64+ *
65+ * @return the SystemKeyManager instance
66+ */
67+ public static SystemKeyManager getInstance () {
68+ return instance ;
69+ }
70+
71+ /**
72+ * Gets a key by name. Returns null if key doesn't exist.
73+ *
74+ * @param keyName the name of the key to get
75+ * @return the SecretKey, or null if not found
76+ */
77+ public SecretKey getKey (String keyName ) {
78+ if (keyName == null || keyName .isEmpty ()) {
79+ keyName = DEFAULT_KEY_NAME ;
80+ }
81+
82+ return keys .get (keyName );
83+ }
84+
85+ /**
86+ * Gets the default key. Returns null if key doesn't exist.
87+ *
88+ * @return the default SecretKey, or null if not found
89+ */
90+ public SecretKey getDefaultKey () {
91+ return getKey (DEFAULT_KEY_NAME );
92+ }
93+
94+ /**
95+ * Gets the Base64-encoded key by name.
96+ *
97+ * @param keyName the name of the key
98+ * @return Base64-encoded key, or null if not found
99+ */
100+ public String getKeyBase64 (String keyName ) {
101+ SecretKey key = getKey (keyName );
102+ return key != null ? Base64 .getEncoder ().encodeToString (key .getEncoded ()) : null ;
103+ }
104+
105+ /**
106+ * Generates a new key from SystemSeed and stores it with the given name.
107+ * The user is responsible for setting the environment variable manually.
108+ *
109+ * @param keyName the name to give the key
110+ * @return the environment variable name where the key should be stored
111+ */
112+ public String generateKey (String keyName ) {
113+ if (keyName == null || keyName .isEmpty ()) {
114+ keyName = DEFAULT_KEY_NAME ;
115+ }
116+
117+ keys .computeIfAbsent (keyName , k -> {
118+ try {
119+ byte [] seed = SystemSeed .getSeed (0 , 32 );
120+ KeyGenerator keyGen = KeyGenerator .getInstance ("AES" );
121+ keyGen .init (KEY_SIZE_BITS , new SecureRandom (seed ));
122+ return keyGen .generateKey ();
123+ } catch (Exception e ) {
124+ throw new RuntimeException ("Failed to generate key from SystemSeed" , e );
125+ }
126+ });
127+
128+ return getEnvVarName (keyName );
129+ }
130+
131+ /**
132+ * Generates a new default key from SystemSeed.
133+ *
134+ * @return the environment variable name where the key is stored
135+ */
136+ public String generateDefaultKey () {
137+ return generateKey (DEFAULT_KEY_NAME );
138+ }
139+
140+ private static final int IV_SIZE_BYTES = 12 ;
141+ private static final int TAG_LENGTH_BITS = 128 ;
142+
143+ /**
144+ * Encrypts data using the default key.
145+ *
146+ * @param data the data to encrypt
147+ * @return encrypted data (with IV prepended)
148+ */
149+ public byte [] encrypt (byte [] data ) {
150+ return encrypt (data , DEFAULT_KEY_NAME );
151+ }
152+
153+ /**
154+ * Encrypts data using a named key.
155+ *
156+ * @param data the data to encrypt
157+ * @param keyName the name of the key to use
158+ * @return encrypted data (with IV prepended)
159+ */
160+ public byte [] encrypt (byte [] data , String keyName ) {
161+ try {
162+ Cipher cipher = Cipher .getInstance ("AES/GCM/NoPadding" );
163+ SecretKey key = getKey (keyName );
164+
165+ byte [] iv = new byte [IV_SIZE_BYTES ];
166+ new SecureRandom ().nextBytes (iv );
167+
168+ GCMParameterSpec gcmParameterSpec = new GCMParameterSpec (TAG_LENGTH_BITS , iv );
169+ cipher .init (Cipher .ENCRYPT_MODE , key , gcmParameterSpec );
170+ byte [] ciphertext = cipher .doFinal (data );
171+
172+ byte [] result = new byte [iv .length + ciphertext .length ];
173+ System .arraycopy (iv , 0 , result , 0 , iv .length );
174+ System .arraycopy (ciphertext , 0 , result , iv .length , ciphertext .length );
175+
176+ return result ;
177+ } catch (Exception e ) {
178+ throw new RuntimeException ("Encryption failed" , e );
179+ }
180+ }
181+
182+ /**
183+ * Decrypts data using the default key.
184+ *
185+ * @param encryptedData the encrypted data (with IV prepended)
186+ * @return decrypted data
187+ */
188+ public byte [] decrypt (byte [] encryptedData ) {
189+ return decrypt (encryptedData , DEFAULT_KEY_NAME );
190+ }
191+
192+ /**
193+ * Decrypts data using a named key.
194+ *
195+ * @param encryptedData the encrypted data (with IV prepended)
196+ * @param keyName the name of the key to use
197+ * @return decrypted data
198+ */
199+ public byte [] decrypt (byte [] encryptedData , String keyName ) {
200+ try {
201+ Cipher cipher = Cipher .getInstance ("AES/GCM/NoPadding" );
202+ SecretKey key = getKey (keyName );
203+
204+ byte [] iv = new byte [IV_SIZE_BYTES ];
205+ System .arraycopy (encryptedData , 0 , iv , 0 , iv .length );
206+
207+ byte [] ciphertext = new byte [encryptedData .length - iv .length ];
208+ System .arraycopy (encryptedData , iv .length , ciphertext , 0 , ciphertext .length );
209+
210+ GCMParameterSpec gcmParameterSpec = new GCMParameterSpec (TAG_LENGTH_BITS , iv );
211+ cipher .init (Cipher .DECRYPT_MODE , key , gcmParameterSpec );
212+ return cipher .doFinal (ciphertext );
213+ } catch (Exception e ) {
214+ throw new RuntimeException ("Decryption failed" , e );
215+ }
216+ }
217+
218+ /**
219+ * Gets the environment variable name for a key.
220+ *
221+ * @param keyName the name of the key
222+ * @return the environment variable name
223+ */
224+ public String getEnvVarName (String keyName ) {
225+ if (keyName == null || keyName .isEmpty ()) {
226+ keyName = DEFAULT_KEY_NAME ;
227+ }
228+ return DEFAULT_ENV_VAR + (DEFAULT_KEY_NAME .equals (keyName ) ? "" : "_" + keyName .toUpperCase ());
229+ }
230+
231+ /**
232+ * Clears all keys (for testing purposes).
233+ */
234+ public void clearKeys () {
235+ keys .clear ();
236+ }
237+ }
0 commit comments