View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.common.crypto;
21  
22  import org.waarp.common.digest.FilesystemBasedDigest;
23  import org.waarp.common.exception.CryptoException;
24  import org.waarp.common.file.FileUtils;
25  import org.waarp.common.logging.WaarpLogger;
26  import org.waarp.common.logging.WaarpLoggerFactory;
27  import org.waarp.common.utility.WaarpStringUtils;
28  
29  import javax.crypto.Cipher;
30  import javax.crypto.KeyGenerator;
31  import javax.crypto.spec.SecretKeySpec;
32  import java.io.DataInputStream;
33  import java.io.File;
34  import java.io.FileInputStream;
35  import java.io.FileOutputStream;
36  import java.io.IOException;
37  import java.security.Key;
38  
39  /**
40   * This class handles method to crypt and decrypt using the chosen
41   * algorithm.<br>
42   *
43   * <br>
44   * Usage:<br>
45   * <ul>
46   * <li>Create a Key object: KeyObject key = new KeyObject();</li>
47   * <li>Create a key:
48   * <ul>
49   * <li>Generate: key.generateKey();<br>
50   * The method key.getSecretKeyInBytes() allow getting the key in Bytes.</li>
51   * <li>From an external source: key.setSecretKey(arrayOfBytes);</li>
52   * </ul>
53   * </li>
54   * <li>To crypt a String in a Hex format: String myStringCrypt =
55   * key.cryptToHex(myString);</li>
56   * <li>To decrypt one string from Hex format to the original String: String
57   * myStringDecrypt =
58   * key.decryptHexInString(myStringCrypte);</li>
59   * </ul>
60   */
61  public abstract class KeyObject {
62    /**
63     * Internal Logger
64     */
65    private static final WaarpLogger logger =
66        WaarpLoggerFactory.getLogger(KeyObject.class);
67  
68    /**
69     * The True Key associated with this object
70     */
71    Key secretKey;
72  
73    /**
74     * Empty constructor
75     */
76    protected KeyObject() {
77    }
78  
79    /**
80     * @return the algorithm used (Java name)
81     */
82    public abstract String getAlgorithm();
83  
84    /**
85     * @return the instance used (Java name)
86     */
87    public abstract String getInstance();
88  
89    /**
90     * @return the size for the algorithm key
91     */
92    public abstract int getKeySize();
93  
94    /**
95     * @return the filename extension to use for this kind of key
96     */
97    public abstract String getFileExtension();
98  
99    /**
100    * @return the key associated with this object
101    */
102   public final Key getSecretKey() {
103     return secretKey;
104   }
105 
106   /**
107    * @return True if this key is ready to be used
108    */
109   public final boolean keyReady() {
110     return secretKey != null;
111   }
112 
113   /**
114    * Returns the key as an array of bytes in order to be stored somewhere else
115    * and retrieved using the
116    * setSecretKey(byte[] keyData) method.
117    *
118    * @return the key as an array of bytes (or null if not ready)
119    */
120   public final byte[] getSecretKeyInBytes() {
121     if (keyReady()) {
122       return secretKey.getEncoded();
123     } else {
124       return null;
125     }
126   }
127 
128   /**
129    * Set the secretKey
130    *
131    * @param secretKey
132    */
133   public final void setSecretKey(final Key secretKey) {
134     this.secretKey = secretKey;
135   }
136 
137   /**
138    * Reconstruct a key from an array of bytes
139    */
140   public final void setSecretKey(final byte[] keyData) {
141     secretKey = new SecretKeySpec(keyData, getAlgorithm());
142   }
143 
144   /**
145    * Create a Key from a File
146    *
147    * @param file
148    *
149    * @throws CryptoException
150    * @throws IOException
151    */
152   public final void setSecretKey(final File file)
153       throws CryptoException, IOException {
154     if (file.canRead()) {
155       final int len = (int) file.length();
156       final byte[] key = new byte[len];
157       final FileInputStream inputStream;
158       inputStream = new FileInputStream(file);
159       final DataInputStream dis = new DataInputStream(inputStream);
160       try {
161         dis.readFully(key);
162       } finally {
163         FileUtils.close(dis);
164       }
165       setSecretKey(key);
166     } else {
167       throw new CryptoException("Cannot read crypto file: " + file);
168     }
169   }
170 
171   /**
172    * Save a Key to a File
173    *
174    * @param file
175    *
176    * @throws CryptoException
177    * @throws IOException
178    */
179   public final void saveSecretKey(final File file)
180       throws CryptoException, IOException {
181     if (keyReady() && (!file.exists() || file.canWrite())) {
182       final byte[] key = getSecretKeyInBytes();
183       final FileOutputStream outputStream = new FileOutputStream(file);
184       try {
185         outputStream.write(key);
186         outputStream.flush();
187       } finally {
188         FileUtils.close(outputStream);
189       }
190     } else {
191       throw new CryptoException("Cannot read crypto file");
192     }
193   }
194 
195   /**
196    * Generate a key from nothing
197    *
198    * @throws Exception
199    */
200   public final void generateKey() throws Exception {
201     try {
202       final KeyGenerator keyGen = KeyGenerator.getInstance(getAlgorithm());
203       keyGen.init(getKeySize());
204       secretKey = keyGen.generateKey();
205     } catch (final Exception e) {
206       logger.warn("GenerateKey Error", e);
207       throw e;
208     }
209   }
210 
211   /**
212    * Returns a cipher for encryption associated with the key
213    *
214    * @return the cipher for encryption or null if it fails in case Encryption
215    *     method or key is incorrect
216    */
217   public Cipher toCrypt() {
218     final Cipher cipher;
219     try {
220       cipher = Cipher.getInstance(getInstance());
221       cipher.init(Cipher.ENCRYPT_MODE, secretKey);
222     } catch (final Exception e) {
223       logger.warn("Crypt Error", e);
224       return null;
225     }
226     return cipher;
227   }
228 
229   /**
230    * Crypt one array of bytes and returns the crypted array of bytes
231    *
232    * @param plaintext
233    *
234    * @return the crypted array of bytes
235    *
236    * @throws Exception
237    */
238   public byte[] crypt(final byte[] plaintext) throws Exception {
239     if (!keyReady()) {
240       throw new CryptoException("Key not Ready");
241     }
242     try {
243       final Cipher cipher = Cipher.getInstance(getInstance());
244       cipher.init(Cipher.ENCRYPT_MODE, secretKey);
245       return cipher.doFinal(plaintext);
246     } catch (final Exception e) {
247       logger.warn("Crypt Error", e);
248       throw e;
249     }
250   }
251 
252   /**
253    * Crypt one array of bytes and returns the crypted String as HEX format
254    *
255    * @param plaintext
256    *
257    * @return the crypted String as HEX format
258    *
259    * @throws Exception
260    */
261   public final String cryptToHex(final byte[] plaintext) throws Exception {
262     final byte[] result = crypt(plaintext);
263     return encodeHex(result);
264   }
265 
266   /**
267    * Crypt one String and returns the crypted array of bytes
268    *
269    * @param plaintext
270    *
271    * @return the crypted array of bytes
272    *
273    * @throws Exception
274    */
275   public final byte[] crypt(final String plaintext) throws Exception {
276     return crypt(plaintext.getBytes(WaarpStringUtils.UTF8));
277   }
278 
279   /**
280    * Crypt one String and returns the crypted String as HEX format
281    *
282    * @param plaintext
283    *
284    * @return the crypted String as HEX format
285    *
286    * @throws Exception
287    */
288   public final String cryptToHex(final String plaintext) throws Exception {
289     return cryptToHex(plaintext.getBytes(WaarpStringUtils.UTF8));
290   }
291 
292   /**
293    * Returns a cipher for decryption associated with the key
294    *
295    * @return the cipher for decryption or null if it fails in case Encryption
296    *     method or key is incorrect
297    */
298   public Cipher toDecrypt() {
299     final Cipher cipher;
300     try {
301       cipher = Cipher.getInstance(getAlgorithm());
302       cipher.init(Cipher.DECRYPT_MODE, secretKey);
303     } catch (final Exception e) {
304       logger.warn("Uncrypt Error", e);
305       return null;
306     }
307     return cipher;
308   }
309 
310   /**
311    * Decrypt an array of bytes and returns the uncrypted array of bytes
312    *
313    * @param ciphertext
314    *
315    * @return the uncrypted array of bytes
316    *
317    * @throws Exception
318    */
319   public byte[] decrypt(final byte[] ciphertext) throws Exception {
320     if (!keyReady()) {
321       throw new CryptoException("Key not Ready");
322     }
323     try {
324       final Cipher cipher = Cipher.getInstance(getAlgorithm());
325       cipher.init(Cipher.DECRYPT_MODE, secretKey);
326       return cipher.doFinal(ciphertext);
327     } catch (final Exception e) {
328       logger.warn("Decrypt Error", e);
329       throw e;
330     }
331   }
332 
333   /**
334    * Decrypt an array of bytes and returns the uncrypted String
335    *
336    * @param ciphertext
337    *
338    * @return the uncrypted array of bytes
339    *
340    * @throws Exception
341    */
342   public final String decryptInString(final byte[] ciphertext)
343       throws Exception {
344     return new String(decrypt(ciphertext), WaarpStringUtils.UTF8);
345   }
346 
347   /**
348    * Decrypt a String as HEX format representing a crypted array of bytes and
349    * returns the uncrypted array of
350    * bytes
351    *
352    * @param ciphertext
353    *
354    * @return the uncrypted array of bytes
355    *
356    * @throws Exception
357    */
358   public final byte[] decryptHexInBytes(final String ciphertext)
359       throws Exception {
360     final byte[] arrayBytes = decodeHex(ciphertext);
361     return decrypt(arrayBytes);
362   }
363 
364   /**
365    * Decrypt an array of bytes as HEX format representing a crypted array of
366    * bytes and returns the uncrypted
367    * array of bytes
368    *
369    * @param ciphertext
370    *
371    * @return the uncrypted array of bytes
372    *
373    * @throws Exception
374    */
375   public final byte[] decryptHexInBytes(final byte[] ciphertext)
376       throws Exception {
377     final byte[] arrayBytes =
378         decodeHex(new String(ciphertext, WaarpStringUtils.UTF8));
379     return decrypt(arrayBytes);
380   }
381 
382   /**
383    * Decrypt a String as HEX format representing a crypted array of bytes and
384    * returns the uncrypted String
385    *
386    * @param ciphertext
387    *
388    * @return the uncrypted String
389    *
390    * @throws Exception
391    */
392   public final String decryptHexInString(final String ciphertext)
393       throws Exception {
394     return new String(decryptHexInBytes(ciphertext), WaarpStringUtils.UTF8);
395   }
396 
397   /**
398    * Decrypt a String as HEX format representing a crypted String and
399    * returns the uncrypted String
400    *
401    * @param ciphertext
402    *
403    * @return the uncrypted String
404    *
405    * @throws Exception
406    */
407   public final String decryptHexInString(final byte[] ciphertext)
408       throws Exception {
409     return new String(decryptHexInBytes(ciphertext), WaarpStringUtils.UTF8);
410   }
411 
412   /**
413    * Decode from a file containing a HEX crypted string
414    *
415    * @param file
416    *
417    * @return the decoded uncrypted content of the file
418    *
419    * @throws Exception
420    */
421   public final byte[] decryptHexFile(final File file) throws Exception {
422     if (file.length() > Integer.MAX_VALUE) {
423       throw new IOException(
424           "File too big to be decoded into an array of bytes");
425     }
426     byte[] byteKeys = new byte[(int) file.length()];
427     FileInputStream inputStream = null;
428     DataInputStream dis = null;
429     try {
430       inputStream = new FileInputStream(file);
431       dis = new DataInputStream(inputStream);
432       dis.readFully(byteKeys);
433       FileUtils.close(dis);
434       final String skey = new String(byteKeys, WaarpStringUtils.UTF8);
435       // decrypt it
436       byteKeys = decryptHexInBytes(skey);
437       return byteKeys;
438     } finally {
439       FileUtils.close(dis);
440       FileUtils.close(inputStream);
441     }
442   }
443 
444   /**
445    * @param encoded
446    *
447    * @return the array of bytes from encoded String (HEX)
448    */
449   public final byte[] decodeHex(final String encoded) {
450     return FilesystemBasedDigest.getFromHex(encoded);
451   }
452 
453   /**
454    * @param bytes
455    *
456    * @return The encoded array of bytes in HEX
457    */
458   public final String encodeHex(final byte[] bytes) {
459     return FilesystemBasedDigest.getHex(bytes);
460   }
461 }