001/* 002 * Copyright 2018 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.plugins.messagingconnector; 017 018import java.io.BufferedReader; 019import java.io.File; 020import java.io.FileReader; 021import java.io.FileWriter; 022import java.io.IOException; 023import java.io.UnsupportedEncodingException; 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.commons.codec.digest.Sha2Crypt; 037import org.apache.commons.lang3.StringUtils; 038 039import org.ametys.runtime.plugin.component.AbstractLogEnabled; 040import org.ametys.runtime.util.AmetysHomeHelper; 041 042/** 043 * Helper to encrypt/decrypt some text 044 */ 045public class CryptoHelper extends AbstractLogEnabled implements Component 046{ 047 /** Avalon Role */ 048 public static final String ROLE = CryptoHelper.class.getName(); 049 050 private static final String CHARSET = "UTF-8"; 051 private static final String CIPHER_ALGORITHM = "AES"; 052 private static final String KEY_ALGORITHM = "AES"; 053 private static final String PASS_HASH_ALGORITHM = "SHA-256"; 054 055 private String _cryptoKey; 056 057 /** 058 * Decrypt a string (base64) using the selected key 059 * @param encryptedValue the encrypted value 060 * @param key The key used to decrypt value 061 * @return the decrypted String 062 */ 063 public String decrypt(String encryptedValue, String key) 064 { 065 if (encryptedValue == null) 066 { 067 return null; 068 } 069 try 070 { 071 Cipher cipher = _buildCipher(key, Cipher.DECRYPT_MODE); 072 byte[] encryptedData = Base64.getDecoder().decode(encryptedValue); 073 byte[] data = cipher.doFinal(encryptedData); 074 return new String(data, CHARSET); 075 } 076 catch (Exception e) 077 { 078 throw new RuntimeException(e); 079 } 080 } 081 082 /** 083 * Decrypt a string (base64) using the generated key 084 * @param encryptedValue the encrypted value 085 * @return the decrypted String 086 */ 087 public String decrypt(String encryptedValue) 088 { 089 String key = getCryptoKey(); 090 return decrypt(encryptedValue, key); 091 } 092 093 /** 094 * Encrypt (base64) a string using the selected key 095 * @param data input data 096 * @param key the key used to encrypt 097 * @return the base64 value of the encrypted data 098 */ 099 public String encrypt(String data, String key) 100 { 101 if (data == null) 102 { 103 return null; 104 } 105 try 106 { 107 Cipher cipher = _buildCipher(key, Cipher.ENCRYPT_MODE); 108 byte[] dataToSend = data.getBytes(CHARSET); 109 byte[] encryptedData = cipher.doFinal(dataToSend); 110 return Base64.getEncoder().encodeToString(encryptedData); 111 112 } 113 catch (Exception e) 114 { 115 throw new RuntimeException(e); 116 } 117 } 118 119 /** 120 * Encrypt (base64) a string using the generated key 121 * @param data input data 122 * @return the base64 value of the encrypted data 123 */ 124 public String encrypt(String data) 125 { 126 String key = getCryptoKey(); 127 return encrypt(data, key); 128 } 129 130 /** 131 * Get the generated crypto key 132 * @return the generated crypto key 133 */ 134 public String getCryptoKey() 135 { 136 if (_cryptoKey == null) 137 { 138 File cryptoFile = new File(AmetysHomeHelper.getAmetysHomeConfig(), "messaging-connector.key"); 139 if (cryptoFile.exists()) 140 { 141 if (cryptoFile.canRead()) 142 { 143 try (BufferedReader reader = new BufferedReader(new FileReader(cryptoFile))) 144 { 145 String line = reader.readLine(); 146 if (!StringUtils.isEmpty(line)) 147 { 148 _cryptoKey = line.trim(); 149 } 150 else 151 { 152 _cryptoKey = _writeKeyInFile(cryptoFile); 153 } 154 } 155 catch (IOException e) 156 { 157 getLogger().error("Unable to read the crypto key for messaging connector from file {}", cryptoFile.getAbsolutePath(), e); 158 } 159 } 160 else 161 { 162 getLogger().error("Unable to read the crypto key for messaging connector from file {}", cryptoFile.getAbsolutePath()); 163 } 164 } 165 else 166 { 167 try 168 { 169 _cryptoKey = _writeKeyInFile(cryptoFile); 170 } 171 catch (IOException e) 172 { 173 getLogger().error("Unable to write the crypto key for messaging connector in file {}", cryptoFile.getAbsolutePath(), e); 174 } 175 } 176 } 177 return _cryptoKey; 178 } 179 180 /** 181 * Will create a new random key based on a sha256 hash of the current time, and write it to a file 182 * @param file file where to write 183 * @return the generated password 184 * @throws IOException if something went wrong when writting the file 185 */ 186 private String _writeKeyInFile(File file) throws IOException 187 { 188 try (FileWriter fw = new FileWriter(file)) 189 { 190 String basePassword = ZonedDateTime.now().toString(); 191 String key = Sha2Crypt.sha256Crypt(basePassword.getBytes()); 192 fw.write(key); 193 return key; 194 } 195 } 196 197 private Cipher _buildCipher(String password, int mode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException 198 { 199 Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); 200 Key key = _buildKey(password); 201 cipher.init(mode, key); 202 return cipher; 203 } 204 205 private Key _buildKey(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException 206 { 207 MessageDigest digester = MessageDigest.getInstance(PASS_HASH_ALGORITHM); 208 digester.update(String.valueOf(password).getBytes(CHARSET)); 209 byte[] key = digester.digest(); 210 return new SecretKeySpec(key, KEY_ALGORITHM); 211 } 212 213}