/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.tsdr.spi.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.HashSet; import java.util.Set; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import javax.xml.transform.stream.StreamSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Data Encrypter is a simple API to encrypt and decrypt strings based on a key generated by the GenerateKey class. * If the key does not exist, it will generate it once and the use the same key to encrypt and decrypt the Strings. * The usage is very simple: * Use the method "encrypt" with a String to encrypt, the result is an encrypted String. * Use the method "decrypt" with an encrypted String to decrypt it. * Please note, if a string is encrypted with a key and a new key is generated afterwards, you will be unable to descrypt the strings. * @author saichler@gmail.com **/ public class DataEncrypter { private static final Logger LOGGER = LoggerFactory.getLogger(DataEncrypter.class); public static final String ENCRYPTED_TAG = "Encrypted:"; public static final String TAG_PASSWORD = "password"; private static final Set<Tag> tagsToEncrypt = new HashSet<Tag>(); private static final String CIPHER_PADDING = "AES/CFB8/NoPadding"; static { init(); } /** * Read the key and install the different tags to seek in the xml config files * Currently it is only seeking "password" **/ private static final void init() { tagsToEncrypt.add(new Tag(TAG_PASSWORD)); if (GenerateKey.getKey() == null) { File keyFile = new File(GenerateKey.PATH_TO_KEY); if(keyFile.exists()) { int length = (int) keyFile.length(); byte keyData[] = new byte[length]; FileInputStream in = null; try { in = new FileInputStream(keyFile); in.read(keyData); } catch (FileNotFoundException e) { LOGGER.error("Key file was not found",e); } catch (IOException e) { LOGGER.error("Could not read key file",e); }finally { if(in!=null){ try { in.close(); } catch (IOException e) { LOGGER.error("Could not close key file",e); } } } GenerateKey.setKey(new SecretKeySpec(keyData, GenerateKey.KEY_METHOD)); }else{ GenerateKey.generateKey(); } } } public static final void addTag(final String tag){ tagsToEncrypt.add(new Tag(tag)); } /** * Encrypt a string and return its encrypted string representation with a prefix encrypted tag. * The method with encrypt the string only if there is a valid key in the GenerateKey.key member. * If the string is already encrypted, it will not encrypt it twice. * * @param str The String to be encrypted * @return The Encrypted String representation with a prefix tag */ public static final String encrypt(final String str) { // No Key, hence disabled if (GenerateKey.getKey() == null) { return str; } if (str==null){ return str; } //already encrypted if (str.startsWith(ENCRYPTED_TAG)) { return str; } Cipher cr = null; try { cr = Cipher.getInstance(CIPHER_PADDING); } catch (NoSuchAlgorithmException e) { LOGGER.error("Failed to get Cipher",e); return str; } catch (NoSuchPaddingException e) { LOGGER.error("Failed to set Padding",e); return str; } try { cr.init(Cipher.ENCRYPT_MODE, GenerateKey.getKey(), GenerateKey.getIvSpec()); } catch (InvalidKeyException e) { LOGGER.error("Invalide key",e); return str; } catch (InvalidAlgorithmParameterException e) { LOGGER.error("Invalide algorithm",e); return str; } final ByteArrayOutputStream bout = new ByteArrayOutputStream(); final CipherOutputStream out = new CipherOutputStream(bout, cr); try { final byte[] data = str.getBytes(); out.write(data); final byte[] encData = bout.toByteArray(); return ENCRYPTED_TAG + DatatypeConverter.printBase64Binary(encData); }catch(IOException e){ LOGGER.error("Could not encrypt String",e); }finally { if(out!=null){ try { out.close(); } catch (IOException e) { LOGGER.error("Could not close stream",e); } } } return str; } /** * Decrypt a tagged encrypted by the "encrypt" method. Will not decrypt if the string is not encrypted. * @param encStr The Tagged Encrypted String * @return The unencrypted string. */ public final static String decrypt(final String encStr) { // No Key, hence disabled if (GenerateKey.getKey() == null) { return encStr; } if(encStr==null) { return null; } //is not encrypted with this util if (!encStr.startsWith(ENCRYPTED_TAG)) { return encStr; } Cipher cr = null; try { cr = Cipher.getInstance(CIPHER_PADDING); } catch (NoSuchAlgorithmException e) { LOGGER.error("Can't find Cipher",e); return encStr; } catch (NoSuchPaddingException e) { LOGGER.error("Failed to set Padding",e); return encStr; } try { cr.init(Cipher.DECRYPT_MODE, GenerateKey.getKey(), GenerateKey.getIvSpec()); } catch (InvalidKeyException e) { LOGGER.error("Invalide Key",e); return encStr; } catch (InvalidAlgorithmParameterException e) { LOGGER.error("Invalide algorithm",e); return encStr; } final byte encData[] = DatatypeConverter.parseBase64Binary(encStr.substring(ENCRYPTED_TAG.length())); final ByteArrayInputStream bin = new ByteArrayInputStream(encData); final CipherInputStream in = new CipherInputStream(bin, cr); final byte data[] = new byte[encStr.length() * 2]; try { in.read(data); return new String(data).trim(); }catch(IOException e){ LOGGER.error("Could not decrypt string",e); }finally { if(in!=null){ try { in.close(); } catch (IOException e) { LOGGER.error("Could not close stream",e); } } } return encStr; } /** * Loads the a text file content */ private final static String loadFileContent(final String fileName) { final File f = new File(fileName); final byte data[] = new byte[(int) f.length()]; FileInputStream in=null; try { in = new FileInputStream(f); in.read(data); }catch(IOException e){ LOGGER.error("Could not read file",e); }finally { try { if(in!=null) { in.close(); } }catch(IOException e){ LOGGER.error("could not close stream",e); } } return new String(data); } /** * Receive a filename and seeks all the defined tags in it and encrypt them. * @param filename The filename to seek the tags and encrypt */ public static final void encryptCredentialAttributes(final String filename) { // No Key, hence disabled if (GenerateKey.getKey() == null) { return; } String fileContent = loadFileContent(filename); if (fileContent == null) { return; } String fileContentInLowerCase = fileContent.toLowerCase(); boolean encryptedAValue = false; for (Tag t : tagsToEncrypt) { t.reset(); String data = t.next(fileContentInLowerCase, fileContent); while (data != null) { if (data.startsWith(ENCRYPTED_TAG)) { data = t.next(fileContentInLowerCase, fileContent); } else { encryptedAValue = true; final String eData = encrypt(data); fileContent = fileContent.substring(0, t.tagLabelEnd + 1)+eData+fileContent.substring(t.tagDataEnd); fileContentInLowerCase = fileContent.toLowerCase(); data = t.next(fileContentInLowerCase, fileContent); } } } if (encryptedAValue) { FileOutputStream out = null; try { out = new FileOutputStream(filename); out.write(fileContent.getBytes()); } catch (IOException e) { LOGGER.error("",e); }finally { if(out!=null){ try { out.close(); } catch (IOException e) { LOGGER.error("",e); } } } } } /** * Receive a filename and seeks all the defined tags in it and decrypt them. * @param filename - The filename to seek the tags and decrypt * @return - StreamSource object */ public final static StreamSource decryptCredentialAttributes(final String filename) { if(filename==null) { return null; } String fileContent = loadFileContent(filename); // No Key, hence disabled if (GenerateKey.getKey() == null) { return new StreamSource(new File(filename)); } if (fileContent == null) { return new StreamSource(new File(filename)); } int index = fileContent.indexOf(ENCRYPTED_TAG); while (index != -1) { int index1 = fileContent.indexOf("<", index); String eData = fileContent.substring(index, index1); String data = decrypt(eData); fileContent = fileContent.substring(0, index) + data + fileContent.substring(index1); index = fileContent.indexOf(ENCRYPTED_TAG); } return new StreamSource(new ByteArrayInputStream(fileContent.getBytes())); } /** * The Tag class represents a tag in an xml file that its value should be encrypted, * for example the tag "<password>" and value is "admin", e.g. "<password>admin</password>", the "admin" should be encrypted * in the XML file. */ private static final class Tag { private String startTag = null; private int tagLabelStart = -1; private int tagLabelEnd = -1; private int tagDataEnd = -1; public Tag(String _tag) { this.startTag = "<" + _tag; } /* Reset the locations of tag last position. */ private void reset() { this.tagLabelStart = -1; this.tagLabelEnd = -1; this.tagDataEnd = -1; } /* Gets the next tag value to encrypt. */ private String next(String lowerCase, String originalTXT) { tagLabelStart = lowerCase.indexOf(startTag, tagLabelStart + 1); if (tagLabelStart == -1) { return null; } tagLabelEnd = lowerCase.indexOf(">", tagLabelStart); if (tagLabelEnd == -1) { return null; } tagDataEnd = lowerCase.indexOf("<", tagLabelStart + 1); if (tagDataEnd == -1 || tagDataEnd < tagLabelEnd) { return null; } return originalTXT.substring(tagLabelEnd + 1, tagDataEnd); } } }