|
|
@@ -0,0 +1,148 @@
|
|
|
+package com.kawayi.app;
|
|
|
+
|
|
|
+import javax.crypto.Cipher;
|
|
|
+import javax.crypto.spec.IvParameterSpec;
|
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
|
+import java.io.UnsupportedEncodingException;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.security.DigestException;
|
|
|
+import java.security.MessageDigest;
|
|
|
+import java.security.NoSuchAlgorithmException;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Base64;
|
|
|
+
|
|
|
+public class AES {
|
|
|
+
|
|
|
+ private static SecretKeySpec secretKey;
|
|
|
+ private static byte[] key;
|
|
|
+
|
|
|
+ public static void setKey(String myKey) {
|
|
|
+ MessageDigest sha = null;
|
|
|
+ try {
|
|
|
+ key = myKey.getBytes("UTF-8");
|
|
|
+ sha = MessageDigest.getInstance("SHA-1");
|
|
|
+ key = sha.digest(key);
|
|
|
+ key = Arrays.copyOf(key, 16);
|
|
|
+ secretKey = new SecretKeySpec(key, "AES");
|
|
|
+ } catch (NoSuchAlgorithmException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ } catch (UnsupportedEncodingException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String encrypt(String strToEncrypt, String secret) {
|
|
|
+ try {
|
|
|
+ setKey(secret);
|
|
|
+ Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
|
|
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
|
|
+ return Base64.getEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes("UTF-8")));
|
|
|
+ } catch (Exception e) {
|
|
|
+ System.out.println("Error while encrypting: " + e.toString());
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String decrypt(String strToDecrypt, String secret) {
|
|
|
+ try {
|
|
|
+ setKey(secret);
|
|
|
+ Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
|
|
|
+ cipher.init(Cipher.DECRYPT_MODE, secretKey);
|
|
|
+ return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)));
|
|
|
+ } catch (Exception e) {
|
|
|
+ System.out.println("Error while decrypting: " + e.toString());
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /*----------------------------------------CryptoJS----------------------------------------*/
|
|
|
+
|
|
|
+ /*
|
|
|
+ https://stackoverflow.com/questions/41432896/cryptojs-aes-encryption-and-java-aes-decryption
|
|
|
+ var text = "The quick brown fox jumps over the lazy dog. 👻 👻";
|
|
|
+ var secret = "René Über";
|
|
|
+ var encrypted = CryptoJS.AES.encrypt(text, secret);
|
|
|
+ encrypted = encrypted.toString();
|
|
|
+ console.log("Cipher text: " + encrypted);
|
|
|
+ Crypto.js 加密和 Java解密
|
|
|
+ */
|
|
|
+ public static String decryptKawayi(String cipherText, String secret) throws Exception {
|
|
|
+ byte[] cipherData = Base64.getDecoder().decode(cipherText);
|
|
|
+ byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
|
|
|
+
|
|
|
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
|
|
|
+ final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5);
|
|
|
+ SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
|
|
|
+ IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
|
|
|
+
|
|
|
+ byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
|
|
|
+ Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
|
+ aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
|
|
|
+ byte[] decryptedData = aesCBC.doFinal(encrypted);
|
|
|
+
|
|
|
+ return new String(decryptedData, StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Generates a key and an initialization vector (IV) with the given salt and password.
|
|
|
+ * <p>
|
|
|
+ * This method is equivalent to OpenSSL's EVP_BytesToKey function
|
|
|
+ * (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
|
|
|
+ * By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data.
|
|
|
+ * </p>
|
|
|
+ * @param keyLength the length of the generated key (in bytes)
|
|
|
+ * @param ivLength the length of the generated IV (in bytes)
|
|
|
+ * @param iterations the number of digestion rounds
|
|
|
+ * @param salt the salt data (8 bytes of data or <code>null</code>)
|
|
|
+ * @param password the password data (optional)
|
|
|
+ * @param md the message digest algorithm to use
|
|
|
+ * @return an two-element array with the generated key and IV
|
|
|
+ */
|
|
|
+ public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
|
|
|
+
|
|
|
+ int digestLength = md.getDigestLength();
|
|
|
+ int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
|
|
|
+ byte[] generatedData = new byte[requiredLength];
|
|
|
+ int generatedLength = 0;
|
|
|
+
|
|
|
+ try {
|
|
|
+ md.reset();
|
|
|
+
|
|
|
+ // Repeat process until sufficient data has been generated
|
|
|
+ while (generatedLength < keyLength + ivLength) {
|
|
|
+
|
|
|
+ // Digest data (last digest if available, password data, salt if available)
|
|
|
+ if (generatedLength > 0)
|
|
|
+ md.update(generatedData, generatedLength - digestLength, digestLength);
|
|
|
+ md.update(password);
|
|
|
+ if (salt != null)
|
|
|
+ md.update(salt, 0, 8);
|
|
|
+ md.digest(generatedData, generatedLength, digestLength);
|
|
|
+
|
|
|
+ // additional rounds
|
|
|
+ for (int i = 1; i < iterations; i++) {
|
|
|
+ md.update(generatedData, generatedLength, digestLength);
|
|
|
+ md.digest(generatedData, generatedLength, digestLength);
|
|
|
+ }
|
|
|
+
|
|
|
+ generatedLength += digestLength;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Copy key and IV into separate byte arrays
|
|
|
+ byte[][] result = new byte[2][];
|
|
|
+ result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
|
|
|
+ if (ivLength > 0)
|
|
|
+ result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
|
|
|
+
|
|
|
+ return result;
|
|
|
+
|
|
|
+ } catch (DigestException e) {
|
|
|
+ throw new RuntimeException(e);
|
|
|
+
|
|
|
+ } finally {
|
|
|
+ // Clean out temporary data
|
|
|
+ Arrays.fill(generatedData, (byte)0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|