/*
* Copyright (C) NetStruxr, Inc. All rights reserved.
*
* This software is published under the terms of the NetStruxr
* Public Software License version 0.5, a copy of which has been
* included with this distribution in the LICENSE.NPL file. */
package er.extensions.crypting;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Base64;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSForwardException;
import com.webobjects.foundation.NSMutableDictionary;
import er.extensions.foundation.ERXProperties;
import er.extensions.foundation.ERXStringUtilities;
/**
* Provides a wrapper around common encryption and decryption operations.
* ERXCrypto provides built-in support for DES and Blowfish crypters. You can
* use the "er.extensions.ERXCrypto.crypters" property to override or provide
* your own. If you only want DES and/or Blowfish, you don't need to set
* crypters yourself.
*
* @author ?
* @property er.extensions.ERXCrypto.default the name of the default crypter
* algorithm (default = "Blowfish")
* @property er.extensions.ERXCrypto.crypters comma-separated list of crypter
* algorithms (i.e. "DES,Blowfish")
* @property er.extensions.ERXCrypto.crypter.[Algorithm] crypter class name,
* should be one for each algorithm in crypters list (i.e.
* er.extensions.ERXCrypto.crypter.DES)
*/
public class ERXCrypto {
/**
* The constant for the DES encryption algorithm.
*/
public static final String DES = "DES";
/**
* The constant for the Blowfish encryption algorithm.
*/
public static final String BLOWFISH = "Blowfish";
/**
* The constant for the AES encryption algorithm.
*/
public static final String AES = "AES";
private static NSMutableDictionary<String, ERXCrypterInterface> _crypters;
private static synchronized NSMutableDictionary<String, ERXCrypterInterface> crypters() {
if (_crypters == null) {
_crypters = new NSMutableDictionary<>();
_crypters.setObjectForKey(new ERXDESCrypter(), ERXCrypto.DES);
_crypters.setObjectForKey(new ERXBlowfishCrypter(), ERXCrypto.BLOWFISH);
_crypters.setObjectForKey(new ERXAESCrypter(), ERXCrypto.AES);
NSArray<String> crypterAlgorithms = ERXProperties.componentsSeparatedByString("er.extensions.ERXCrypto.crypters", ",");
if (crypterAlgorithms != null) {
for (String crypterAlgorithm : crypterAlgorithms) {
String crypterClassName = ERXProperties.stringForKey("er.extensions.ERXCrypto.crypter." + crypterAlgorithm);
if (crypterClassName == null) {
throw new IllegalArgumentException("You did not provide a crypter class definition for 'er.extensions.ERXCrypto.crypter." + crypterAlgorithm + "'.");
}
try {
ERXCrypterInterface crypter = Class.forName(crypterClassName).asSubclass(ERXCrypterInterface.class).newInstance();
_crypters.setObjectForKey(crypter, crypterAlgorithm);
}
catch (Exception e) {
throw new NSForwardException(e, "Failed to create " + crypterAlgorithm + " crypter '" + crypterClassName + "'.");
}
}
}
}
return _crypters;
}
/**
* Returns the default crypter. By default this is Blowfish, but you can
* override the choice by setting er.extensions.ERXCrypto.default.
*
* @return the default crypter
*/
public static ERXCrypterInterface defaultCrypter() {
String defaultCrypterAlgorithm = ERXProperties.stringForKeyWithDefault("er.extensions.ERXCrypto.default", ERXCrypto.BLOWFISH);
return ERXCrypto.crypterForAlgorithm(defaultCrypterAlgorithm);
}
/**
* Sets the crypter for the given algorithm.
*
* @param crypter
* the crypter to use
* @param algorithm
* the algorithm name
*/
public static void setCrypterForAlgorithm(ERXCrypterInterface crypter, String algorithm) {
NSMutableDictionary<String, ERXCrypterInterface> crypters = ERXCrypto.crypters();
crypters.setObjectForKey(crypter, algorithm);
}
/**
* Returns the crypter for the given algorithm. By default, DES and Blowfish
* are available ("DES", "Blowfish", etc).
*
* @param algorithm
* the algorithm to lookup
* @return the corresponding crypter
* @throws IllegalArgumentException
* if there is no crypter for the given algorithm
*/
public static ERXCrypterInterface crypterForAlgorithm(String algorithm) {
NSMutableDictionary<String, ERXCrypterInterface> crypters = ERXCrypto.crypters();
ERXCrypterInterface crypter = crypters.objectForKey(algorithm);
if (crypter == null) {
throw new IllegalArgumentException("Unknown encryption algorithm '" + algorithm + "'.");
}
return crypter;
}
/**
* Decodes all of the values from a given dictionary using the default
* crypter.
*
* @param dict
* dictionary of key value pairs where the values are encoded
* strings
* @return a dictionary of decoded key-value pairs
*/
public static NSMutableDictionary<String, String> decodedFormValuesDictionary(NSDictionary<String, NSArray<String>> dict) {
NSMutableDictionary<String, String> result = new NSMutableDictionary<>();
for (String key: dict.allKeys()) {
NSArray<String> objects = dict.objectForKey(key);
String value = ERXCrypto.defaultCrypter().decrypt(objects.lastObject()).trim();
result.setObjectForKey(value, key);
}
return result;
}
/*
* Hashing and encryption methods
*/
/**
* Uses the SHA hash algorithm found in the Sun JCE to hash the passed in
* String. This String is then base64 encoded and returned.
*
* @param v the string to encode
* @return the encoded string
*/
public static String base64HashedString(String v) {
String base64HashedPassword = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(v.getBytes());
base64HashedPassword = Base64.encodeBase64String(md.digest());
}
catch (NoSuchAlgorithmException e) {
throw new NSForwardException(e, "Couldn't find the SHA hash algorithm; perhaps you do not have the SunJCE security provider installed properly?");
}
return base64HashedPassword;
}
/**
* SHA-1 encodes a given string. The resulting string is safe to use in urls
* and cookies. From the digest of the string it is nearly impossible to
* determine what the original string was. Running the same string through
* the SHA-1 digest multiple times will always produce the same hash.
*
* @param text
* to be put through the sha digest
* @return hashed form of the given string
*/
public static String shaEncode(String text) {
return algorithmEncode(text, "SHA");
}
/**
* SHA-256 encodes a given string. The resulting string is safe to use in urls
* and cookies. From the digest of the string it is nearly impossible to
* determine what the original string was. Running the same string through
* the SHA-256 digest multiple times will always produce the same hash.
*
* @param text
* to be put through the sha digest
* @return hashed form of the given string
*/
public static String sha256Encode(String text) {
return algorithmEncode(text, "SHA-256");
}
/**
* SHA-384 encodes a given string. The resulting string is safe to use in urls
* and cookies. From the digest of the string it is nearly impossible to
* determine what the original string was. Running the same string through
* the SHA-384 digest multiple times will always produce the same hash.
*
* @param text
* to be put through the sha digest
* @return hashed form of the given string
*/
public static String sha384Encode(String text) {
return algorithmEncode(text, "SHA-384");
}
/**
* SHA-512 encodes a given string. The resulting string is safe to use in urls
* and cookies. From the digest of the string it is nearly impossible to
* determine what the original string was. Running the same string through
* the SHA-512 digest multiple times will always produce the same hash.
*
* @param text
* to be put through the sha digest
* @return hashed form of the given string
*/
public static String sha512Encode(String text) {
return algorithmEncode(text, "SHA-512");
}
/**
* MD5 encodes a given string. The resulting string is safe to use in urls
* and cookies. From the digest of the string it is nearly impossible to
* determine what the original string was. Running the same string through
* the MD5 digest multiple times will always produce the same hash.
*
* @param text
* to be put through the sha digest
* @return hashed form of the given string
*/
public static String md5Encode(String text) {
return algorithmEncode(text, "MD5");
}
/**
* Encodes a given string with a given algorithm. The resulting string is safe
* to use in urls and cookies. From the digest of the string it is nearly
* impossible to determine what the original string was. Running the same string
* through the algorithm digest multiple times will always produce the same hash.
*
* @param text
* to be put through the algorithm digest
* @param algorithmName
* the algorithm to use (e.g. SHA, SHA-256, ...)
* @return hashed form of the given string
*/
public static String algorithmEncode(String text, String algorithmName) {
if (text == null || algorithmName == null) {
return text;
}
byte[] buf = text.getBytes();
try {
MessageDigest md = MessageDigest.getInstance(algorithmName);
md.update(buf);
return ERXStringUtilities.byteArrayToHexString(md.digest());
}
catch (NoSuchAlgorithmException ex) {
throw new NSForwardException(ex, "Couldn't find the algorithm '" + algorithmName
+ "'; perhaps you do not have the SunJCE security provider installed properly?");
}
}
/**
* Base64 encodes the passed in byte[]
*
* @param byteArray the byte array to encode
* @return the encoded string
*/
public static String base64Encode(byte[] byteArray) {
return Base64.encodeBase64String(byteArray);
}
/**
* Base64url encodes the passed in byte[]
*
* @param byteArray the byte array to URL encode
* @return the encoded string
*/
public static String base64urlEncode(byte[] byteArray) {
return Base64.encodeBase64URLSafeString(byteArray);
}
/**
* Base64 decodes the passed in String
*
* @param s the string to decode
* @return a byte array of the decoded string
* @throws IOException if the decode fails
*/
// TODO remove throws declaration when API change is possible
public static byte[] base64Decode(String s) throws IOException {
return Base64.decodeBase64(s);
}
/**
* Run this with ERXMainRunner passing in the plaintext you want to encrypt
* using the default crypter. This is useful if you are using encrypted
* properties and you need a quick way to know what to set the property
* value to.
*
* @param args the plaintext to encrypt
*/
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("Usage: ERXCrypto [plaintext]");
System.out.println(" returns the encrypted form of the given plaintext using the default crypter");
System.exit(0);
}
String plaintext = args[0];
String encrypted = ERXCrypto.defaultCrypter().encrypt(plaintext);
System.out.println("ERXCrypto.main: Encrypted form of '" + plaintext + "' is '" + encrypted + "'");
}
}