All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.odysz.common.AESHelper Maven / Gradle / Ivy

package io.odysz.common;

import static io.odysz.common.LangExt.eq;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Properties;
import java.util.concurrent.locks.ReentrantLock;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.crypto.cipher.CryptoCipher;
import org.apache.commons.crypto.random.CryptoRandom;
import org.apache.commons.crypto.random.CryptoRandomFactory;
import org.apache.commons.crypto.utils.Utils;

/**Static helpers for encrypt/decipher string.
 * @author ody
 */
public class AESHelper {
    static Properties randomProperties = new Properties();
    /**
     * Deprecating static final String transform = "AES/CBC/PKCS5Padding";
* Apache Common Crypto only support PKCS#5 padding, but most js lib support PKCS#7 padding, * This makes trouble when negotiation with those API. * Solution: using no padding here, round the text to 16 or 32 ASCII bytes. */ static final String transform = "AES/CBC/NoPadding"; static CryptoCipher encipher; static ReentrantLock lock; static { randomProperties.put(CryptoRandomFactory.CLASSES_KEY, CryptoRandomFactory.RandomProvider.JAVA.getClassName()); Properties cipherProperties = new Properties(); // causing problem for different environment: // cipherProperties.setProperty(CryptoCipherFactory.CLASSES_KEY, CipherProvider.JCE.getClassName()); try { encipher = Utils.getCipherInstance(transform, cipherProperties); } catch (IOException e) { e.printStackTrace(); } // experiment 10 Dec 2023 // solving crash happened outside the Java Virtual Machine in native code. lock = new ReentrantLock(); } /**TODO move to test * @param args */ public static void main(String[] args) { String s = "plain text 1"; byte[] iv = getRandom(); try { System.out.println("iv:\t\t" + Base64.getEncoder().encodeToString(iv)); String cipher = encrypt(s, "infochange", iv); System.out.println("cipher:\t\t" + cipher); String plain = decrypt(cipher, "infochange", iv); System.out.println("plain-text:\t" + plain); } catch (Exception e) { e.printStackTrace(); } } /** * @return 16 random bytes */ public static byte[] getRandom() { byte[] iv = new byte[16]; try (CryptoRandom random = CryptoRandomFactory.getCryptoRandom(randomProperties)) { random.nextBytes(iv); return iv; }catch(IOException | GeneralSecurityException ex) { ex.printStackTrace(); return null; } } /** * Decrypt then encrypt. * @param cypher Base64 * @param decryptK plain key string * @param decryptIv Base64 * @param encryptK plain key string * @return [cipher-base64, new-iv-base64] * @throws GeneralSecurityException * @throws IOException * @return string[b64(cipher), b64(iv)] */ public static String[] dencrypt(String cypher, String decryptK, String decryptIv, String encryptK) throws GeneralSecurityException, IOException { byte[] iv = AESHelper.decode64(decryptIv); byte[] input = AESHelper.decode64(cypher); byte[] dkb = getUTF8Bytes(pad16_32(decryptK)); // FIXME won't work for non ASCII byte[] plain = decryptEx(input, dkb, iv); byte[] eiv = getRandom(); byte[] ekb = getUTF8Bytes(pad16_32(encryptK)); // FIXME won't work for non ASCII byte[] output = encryptEx(plain, ekb, eiv); String b64 = Base64.getEncoder().encodeToString(output); return new String[] {b64, AESHelper.encode64(eiv)}; } /** * Encrypt plain text to cipher of base 64. * * @param plain * @param key * @param iv * @return * @throws GeneralSecurityException * @throws IOException */ public static String encrypt(String plain, String key, byte[] iv) throws GeneralSecurityException, IOException { if (!plain.trim().equals(plain)) throw new GeneralSecurityException("Plain text to be encrypted can not begin or end with space."); key = pad16_32(key); plain = pad16_32(plain); byte[] input = getUTF8Bytes(plain); byte[] kb = getUTF8Bytes(key); byte[] output = encryptEx(input, kb, iv); String b64 = Base64.getEncoder().encodeToString(output); return b64; } //-------------ody:io.github.odys-z /** * 10 Dec 2024:
* This line causes trouble in JDK 15, Open JDK x64. * * @param input * @param key * @param iv * @return result bytes * @throws GeneralSecurityException * @throws IOException */ static byte[] encryptEx(byte[] input, byte[] key, byte[]iv) throws GeneralSecurityException, IOException { final SecretKeySpec keyspec = new SecretKeySpec(key, "AES"); final IvParameterSpec ivspec = new IvParameterSpec(iv); //Initializes the cipher with ENCRYPT_MODE, key and iv. try { lock.lock(); encipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec); byte[] output = new byte[((input.length)/16 + 2) * 16]; // int finalBytes = encipher.doFinal(input, 0, input.length, output, 0); // above code is incorrect (not working with PKCS#7 padding), // check Apache Common Crypto User Guide: // https://commons.apache.org/proper/commons-crypto/userguide.html // Usage of Byte Array Encryption/Decryption, CipherByteArrayExample.java int updateBytes = encipher.update(input, 0, input.length, output, 0); int finalBytes = encipher.doFinal(input, 0, 0, output, updateBytes); output = Arrays.copyOf(output, updateBytes + finalBytes); encipher.close(); return output; } catch (GeneralSecurityException e) { throw new GeneralSecurityException(e.getMessage()); } finally { lock.unlock(); } } public static String decrypt(String cypher, String key, byte[] iv) throws GeneralSecurityException, IOException { byte[] input = Base64.getDecoder().decode(cypher); // FIXME should padding bytes, not string. byte[] kb = getUTF8Bytes(pad16_32(key)); // FIXME won't work for non ASCII byte[] output = decryptEx(input, kb, iv); String p = setUTF8Bytes(output); // return p.replace("-", ""); return depad16_32(p); } static byte[] decryptEx(byte[] input, byte[] key, byte[]iv) throws GeneralSecurityException, IOException { final SecretKeySpec keyspec = new SecretKeySpec(key, "AES"); final IvParameterSpec ivspec = new IvParameterSpec(iv); encipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec); byte[] output = new byte[((input.length)/ 16 + 2) * 16]; int finalBytes = encipher.doFinal(input, 0, input.length, output, 0); encipher.close(); return Arrays.copyOf(output, finalBytes); } /** * @param s string of ASCII * @return 16 / 32 byte string * @throws GeneralSecurityException */ public static String pad16_32(String s) throws GeneralSecurityException { int l = s.length(); if (l <= 16) return String.format("%1$16s", s).replaceAll(" ", "-"); else if (l <= 32) return String.format("%1$32s", s).replaceAll(" ", "-"); else throw new GeneralSecurityException("Not supported block length(16B/32B): " + s); } private static String depad16_32(String s) throws GeneralSecurityException { int l = s.length(); if (l <= 16) return s.replaceAll("-", " ").trim(); else if (l <= 32) return s.replaceAll("-", " ").trim(); else throw new GeneralSecurityException("Not supported block length(16B/32B): " + s); } /** * Converts String to UTF8 bytes * * @param input the input string * @return UTF8 bytes */ private static byte[] getUTF8Bytes(String input) { return input.getBytes(StandardCharsets.US_ASCII); } /**Converts UTF8 bytes to String * @param input * @return converted result */ private static String setUTF8Bytes(byte[] input) { return new String(input, StandardCharsets.US_ASCII); } public static String encode64(final byte[] bytes) { return Base64.getEncoder().encodeToString(bytes); } /** * @param ifs * @param blockSize default 3 * 1024 * 1024; * @return * @throws IOException */ public static String encode64(final InputStream ifs, int blockSize) throws IOException { blockSize = blockSize > 0 ? blockSize : 3 * 1024 * 1024; if ((blockSize % 12) != 0) throw new IOException ("Block size must be multple of 12."); byte[] chunk = new byte[blockSize]; /* BufferedInputStream in = new BufferedInputStream(ifs, blockSize); Base64.Encoder encoder = Base64.getEncoder(); int len = in.read(chunk); if (len >= 0) return encoder.encodeToString(chunk); else return null; */ return encode64(chunk, ifs, 0, blockSize); } /** * Usage example:
	 *
	byte[] buf = new byte[n * 3];
	int index = 0;
	while (index < file_size) {
		int readlen  = Math.min(buf.length, size - index);
		String str64 = encode64(buf, ifs, index, readlen);
		index += readlen;
		// consumption of str64
		...
	}
* @param buf * @param ifs file input stream * @param start * @param len * @return encoded string * @throws IOException * @throws TransException buffer length is not multiple of 3. */ public static String encode64(byte[] buf, final InputStream ifs, int start, int len) throws IOException { BufferedInputStream in = new BufferedInputStream(ifs, buf.length); Base64.Encoder encoder = Base64.getEncoder(); int readLen = in.read(buf); if (readLen <= 0) return null; else if (readLen == buf.length) return encoder.encodeToString(buf); else // (readLen < buf.length) return encoder.encodeToString(Arrays.copyOf(buf, readLen)); } public static byte[] decode64(String str) { return Base64.getDecoder().decode(str); } /** * Is encrypt(plain, k, v) == cipher? * i.e. encrypt(uid:random, k, iv) == token ? where uid:random = clientoken * * @return true: yes the same * @throws Exception */ public static boolean verifyToken(String requestoken, String myKnowledge, String uid, String key) throws Exception { String[] sstoken = requestoken.split(":"); String enciphered = encrypt(pad16_32(uid + ":" + myKnowledge), key, decode64(sstoken[1])); return eq(enciphered, sstoken[0]); } /** *
	 * ssToken = cipher : iv, len(cipher) = 44
	 * plain = decrypt(cipher, key, iv)
	 * token = encrypt(pad(uid) : plain, key, iv2)
	 * return token : iv2, if cipher.length == 44, len(token : iv2) = 44 + 1 + 24 = 69
	 * 
* * @return token for managed session requests, token:iv2, len = 69 for ssToken.len = 69 * @throws GeneralSecurityException * @throws IOException */ public static String repackSessionToken(String ssToken, String key, String uid) throws GeneralSecurityException, IOException { String[] ss = ssToken.split(":"); String plain = decrypt(ss[0], key, decode64(ss[1])); byte[] iv = getRandom(); String cipher = encrypt(uid + ":" + plain, key, iv); return cipher + ":" + encode64(iv); } /** *
	 * iv = random(16)
	 * token = encrypt(random, key, iv), len(random) = 24
	 * return token : iv,
	 * where len(token) = [(24 + 15) / 16] * 16 * [4/3] = 32 * [4/3] = 44
	 * 
* * @param key * @return 0: string(token : iv), 1: knowledge in base 64 (random token) * @throws GeneralSecurityException * @throws IOException * @since 1.4.37 * @see AESHelperTest#testSessionToken() source */ public static String[] packSessionKey(String key) throws GeneralSecurityException, IOException { byte[] iv = AESHelper.getRandom(); byte[] knows = AESHelper.getRandom(); String token = AESHelper.encode64(knows); return new String[] {AESHelper.encrypt(token, key, iv) + ":" + AESHelper.encode64(iv), token}; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy