001/* 002 * Copyright 2022 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.util; 017 018import java.io.BufferedReader; 019import java.io.File; 020import java.io.FileReader; 021import java.io.FileWriter; 022import java.io.IOException; 023import java.nio.charset.StandardCharsets; 024import java.security.InvalidKeyException; 025import java.security.Key; 026import java.security.MessageDigest; 027import java.security.NoSuchAlgorithmException; 028import java.time.ZonedDateTime; 029import java.util.Base64; 030 031import javax.crypto.Cipher; 032import javax.crypto.NoSuchPaddingException; 033import javax.crypto.spec.SecretKeySpec; 034 035import org.apache.avalon.framework.component.Component; 036import org.apache.avalon.framework.configuration.Configurable; 037import org.apache.avalon.framework.configuration.Configuration; 038import org.apache.avalon.framework.configuration.ConfigurationException; 039import org.apache.commons.codec.digest.Sha2Crypt; 040import org.apache.commons.lang3.StringUtils; 041 042import org.ametys.runtime.plugin.component.AbstractLogEnabled; 043import org.ametys.runtime.util.AmetysHomeHelper; 044 045/** 046 * Helper to encrypt/decrypt some text 047 */ 048public class CryptoHelper extends AbstractLogEnabled implements Component, Configurable 049{ 050 private static final String CIPHER_ALGORITHM = "AES"; 051 private static final String KEY_ALGORITHM = "AES"; 052 private static final String PASS_HASH_ALGORITHM = "SHA-256"; 053 054 private String _cryptoKey; 055 056 private String _filename; 057 058 public void configure(Configuration configuration) throws ConfigurationException 059 { 060 _filename = configuration.getChild("filename").getValue(); 061 } 062 063 /** 064 * Decrypt a string (base64) using the selected key 065 * @param encryptedValue the encrypted value 066 * @param key The key used to decrypt value 067 * @return the decrypted String 068 * @throws WrongKeyException If the key is not the right one 069 */ 070 public String decrypt(String encryptedValue, String key) throws WrongKeyException 071 { 072 if (encryptedValue == null) 073 { 074 return null; 075 } 076 077 Cipher cipher; 078 try 079 { 080 cipher = _buildCipher(key, Cipher.DECRYPT_MODE); 081 } 082 catch (Exception e) 083 { 084 throw new RuntimeException(e); 085 } 086 087 byte[] encryptedData = Base64.getDecoder().decode(encryptedValue); 088 byte[] data; 089 090 try 091 { 092 data = cipher.doFinal(encryptedData); 093 } 094 catch (Exception e) 095 { 096 throw new WrongKeyException(e); 097 } 098 099 return new String(data, StandardCharsets.UTF_8); 100 } 101 102 /** 103 * Decrypt a string (base64) using the generated key 104 * @param encryptedValue the encrypted value 105 * @return the decrypted String 106 */ 107 public String decrypt(String encryptedValue) 108 { 109 String key = getCryptoKey(); 110 return decrypt(encryptedValue, key); 111 } 112 113 /** 114 * Encrypt (base64) a string using the selected key 115 * @param data input data 116 * @param key the key used to encrypt 117 * @return the base64 value of the encrypted data 118 */ 119 public String encrypt(String data, String key) 120 { 121 if (data == null) 122 { 123 return null; 124 } 125 try 126 { 127 Cipher cipher = _buildCipher(key, Cipher.ENCRYPT_MODE); 128 byte[] dataToSend = data.getBytes(StandardCharsets.UTF_8); 129 byte[] encryptedData = cipher.doFinal(dataToSend); 130 return Base64.getEncoder().encodeToString(encryptedData); 131 132 } 133 catch (Exception e) 134 { 135 throw new RuntimeException(e); 136 } 137 } 138 139 /** 140 * Encrypt (base64) a string using the generated key 141 * @param data input data 142 * @return the base64 value of the encrypted data 143 */ 144 public String encrypt(String data) 145 { 146 String key = getCryptoKey(); 147 return encrypt(data, key); 148 } 149 150 /** 151 * Get the generated crypto key 152 * @return the generated crypto key 153 */ 154 public String getCryptoKey() 155 { 156 if (_cryptoKey == null) 157 { 158 File cryptoFile = new File(AmetysHomeHelper.getAmetysHomeConfig(), _filename); 159 if (cryptoFile.exists()) 160 { 161 if (cryptoFile.canRead()) 162 { 163 try (BufferedReader reader = new BufferedReader(new FileReader(cryptoFile))) 164 { 165 String line = reader.readLine(); 166 if (!StringUtils.isEmpty(line)) 167 { 168 _cryptoKey = line.trim(); 169 } 170 else 171 { 172 _cryptoKey = _writeKeyInFile(cryptoFile); 173 } 174 } 175 catch (IOException e) 176 { 177 getLogger().error("Unable to read the crypto key from file {}", cryptoFile.getAbsolutePath(), e); 178 } 179 } 180 else 181 { 182 getLogger().error("Unable to read the crypto key from file {}", cryptoFile.getAbsolutePath()); 183 } 184 } 185 else 186 { 187 try 188 { 189 _cryptoKey = _writeKeyInFile(cryptoFile); 190 } 191 catch (IOException e) 192 { 193 getLogger().error("Unable to write the crypto key in file {}", cryptoFile.getAbsolutePath(), e); 194 } 195 } 196 } 197 return _cryptoKey; 198 } 199 200 /** 201 * Will create a new random key based on a sha256 hash of the current time, and write it to a file 202 * @param file file where to write 203 * @return the generated password 204 * @throws IOException if something went wrong when writting the file 205 */ 206 private String _writeKeyInFile(File file) throws IOException 207 { 208 try (FileWriter fw = new FileWriter(file)) 209 { 210 String basePassword = ZonedDateTime.now().toString(); 211 String key = Sha2Crypt.sha256Crypt(basePassword.getBytes()); 212 fw.write(key); 213 return key; 214 } 215 } 216 217 private Cipher _buildCipher(String password, int mode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException 218 { 219 Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); 220 Key key = _buildKey(password); 221 cipher.init(mode, key); 222 return cipher; 223 } 224 225 private Key _buildKey(String password) throws NoSuchAlgorithmException 226 { 227 MessageDigest digester = MessageDigest.getInstance(PASS_HASH_ALGORITHM); 228 digester.update(String.valueOf(password).getBytes(StandardCharsets.UTF_8)); 229 byte[] key = digester.digest(); 230 return new SecretKeySpec(key, KEY_ALGORITHM); 231 } 232 233 /** 234 * Exception when the decrypt key has not worked 235 */ 236 public static class WrongKeyException extends RuntimeException 237 { 238 WrongKeyException(Exception e) 239 { 240 super(e); 241 } 242 } 243}