package quant.rich.emoney.util; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.util.Base64; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.lang3.StringUtils; import org.bouncycastle.crypto.digests.SHA3Digest; import org.bouncycastle.util.encoders.Hex; import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; import okhttp3.Request; import okhttp3.RequestBody; @Slf4j public class EncryptUtils { private static final byte[] IV = "aesiv_emapp@2018".getBytes(StandardCharsets.UTF_8); private static final String PASSWORD_KEY_STRING_BASE64 = "5pxlF/NrBtxlBve0jQwGEw=="; private static final String PASSWORD_CIPHER_INSTANCE = "AES/CBC/PKCS5Padding"; private static final String AES_DECODE_KEY_STRING_BASE64 = "39KLC1etSfUXuiGxRm5ilDDtqapKXm0COuHxUrWSWlc="; private static final String EM_SIGN_MESS_1 = "23082404151001"; private static final String EM_SIGN_MESS_2 = "994fec3c512f2f7756fd5e4403147f01"; private static final String SLASH = "/"; private static final String COLON = ":"; /** * 加密用于 Emoney 登录的密码 * @param pwd 明文密码 * @return 加密过的密码(Base64 字符串) */ public static String encryptAesForEmoneyPassword(String pwd) { try { byte[] keyBytes = Base64.getDecoder().decode(PASSWORD_KEY_STRING_BASE64); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); IvParameterSpec ivSpec = new IvParameterSpec(IV); Cipher cipher = Cipher.getInstance(PASSWORD_CIPHER_INSTANCE); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); // 执行加密 byte[] encryptedBytes = cipher.doFinal(pwd.getBytes(StandardCharsets.UTF_8)); // 将加密结果转换为Base64字符串 String encryptedString = Base64.getEncoder().encodeToString(encryptedBytes); return encryptedString; } catch (Exception e) { log.error("Encrypt aes for emoney-password failed", e); return null; } } /** * 解密由 Emoney 进行加密过的密码 * @param pwd 加密过的密码(Base64 字符串) * @return 明文密码 */ public static String decryptAesForEmoneyPassword(String pwd) { try { byte[] keyBytes = Base64.getDecoder().decode(PASSWORD_KEY_STRING_BASE64); byte[] passBytes = Base64.getDecoder().decode(pwd); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); IvParameterSpec ivSpec = new IvParameterSpec(IV); Cipher cipher = Cipher.getInstance(PASSWORD_CIPHER_INSTANCE); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); // 执行解密 byte[] decryptedBytes = cipher.doFinal(passBytes); return new String(decryptedBytes, "UTF-8"); } catch (Exception e) { //log.error("Decrypt aes for emoney-password failed", e); return null; } } /** * 使用益盟自带 AES key 对指定字节数组进行解密,一般而言该字节数组通过解码 Base64 字符串得到 * @param bArr * @return */ private static String decryptAesForEmSignMess(byte[] bArr) { try { byte[] keyBytes = Base64.getDecoder().decode(AES_DECODE_KEY_STRING_BASE64); EmoneyBytesResortUtils.d(keyBytes); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, keySpec); return new String(cipher.doFinal(bArr), "UTF-8"); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException e) { e.printStackTrace(); } return null; } /** * 字符串转 32 位小写 md5 字符串 * * @param str * @return */ public static String toMD5String(String str) { if (StringUtils.isEmpty(str)) { return null; } try { return toMD5String(str.getBytes("UTF-8")); } catch (UnsupportedEncodingException e10) { e10.printStackTrace(); return null; } } /** * 字节数组转 32 位小写 md5 字符串 * @param bytes * @return */ public static String toMD5String(byte[] bytes) { if (bytes == null) { return null; } try { MessageDigest instance = MessageDigest.getInstance("MD5"); instance.update(bytes); return bytesToHex(instance.digest()); } catch (NoSuchAlgorithmException e10) { e10.printStackTrace(); return null; } } public static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { // 将字节转换为无符号整数表示的16进制,并且保证每个字节输出两个字符 sb.append(String.format("%02x", b & 0xFF)); } return sb.toString(); } /** * 根据 content、method 和 xProtocolId 生成 EM-Sign,随机字符串和时间戳自动生成 * @param content 请求的字节数组 * @param method 请求方法 * @param xProtocolId 请求 X-Protocol-Id * @return * @throws IOException */ public static String getEMSign( byte[] content, String method, String xProtocolId) throws IOException { String randomString = TextUtils.randomString(10); long timestampFixed = Instant.now().toEpochMilli(); return getEMSign(content, method, xProtocolId, randomString, timestampFixed); } /** * 根据 content、method、xProtocolId、指定的随机 10 位字符串、指定的时间戳生成 EM-Sign * @param content 请求的字节数组 * @param method 请求方法 * @param xProtocolId 请求 X-Protocol-Id * @return * @throws IOException */ public static String getEMSign( byte[] content, String method, String xProtocolId, String randomString, long timestampFixed) throws IOException { // TODO: 空校验、时间戳校验、随机字符串长度校验等防呆操作 StringBuffer plainSignSb = new StringBuffer(); plainSignSb.append(EM_SIGN_MESS_1); plainSignSb.append(method.toUpperCase()); plainSignSb.append(EM_SIGN_MESS_2); plainSignSb.append(SLASH); plainSignSb.append(URLDecoder.decode(xProtocolId, "UTF-8")); plainSignSb.append(timestampFixed); plainSignSb.append(randomString); if (content != null && content.length != 0) { plainSignSb.append(toMD5String(content)); } String md5Sign = EncryptUtils.toMD5String(plainSignSb.toString()); StringBuilder emSignSb = new StringBuilder(); emSignSb.append(EM_SIGN_MESS_1); emSignSb.append(COLON); emSignSb.append(md5Sign); emSignSb.append(COLON); emSignSb.append(randomString); emSignSb.append(COLON); emSignSb.append(timestampFixed); String emSign = emSignSb.toString(); return emSign; } public static byte[] sha3(byte[] bytes, int digit) { SHA3Digest digest = new SHA3Digest(digit); digest.update(bytes, 0, bytes.length); byte[] rsData = new byte[digest.getDigestSize()]; digest.doFinal(rsData, 0); return rsData; } public static String sha256(String input) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(input.getBytes()); StringBuilder hexString = new StringBuilder(); for (byte b : hash) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) { hexString.append('0'); } hexString.append(hex); } return hexString.toString(); } catch (Exception e) { log.error("SHA-256 Hash failed", e); return null; } } public static String sha3(String data, int digit) { return Hex.toHexString(sha3(data.getBytes(), digit)); } public static String toHexString(byte[] data) { return Hex.toHexString(data); } public static void main(String[] args) throws IOException { // EM-Sign: // 23082404151001:356b7041c37848b4f73a8ad5c9416f48:nBZTuInqo1:1721439297485 long timestampFixed = 1721439297485L; String randomString = "nBZTuInqo1"; byte[] mess = Base64.getDecoder().decode("0JryhAshy7JXgP+5sLmWwaurGM3YHWaFbGaKH/mi7oKN+5mlTnEp5LTeFJVtJTTR"); String f13443a = EncryptUtils.decryptAesForEmSignMess(mess); String path = "C:\\Users\\Administrator\\Desktop\\Emoney\\login.request.body"; FileInputStream in = new FileInputStream(new File(path)); byte[] requestBody = new byte[in.available()]; in.read(requestBody); in.close(); String requestBodyStr = "{\"date\":0}";// new String(requestBody, "UTF-8") + '\0'; requestBody = requestBodyStr.getBytes("UTF-8"); RequestBody body = RequestBody.create(requestBodyStr, MediaType.parse("application/json")); okio.Buffer buffer = new okio.Buffer(); body.writeTo(buffer); requestBody = buffer.readByteArray(); String requestBodyMd5; if (requestBody == null || requestBody.length == 0) { requestBodyMd5 = ""; } else { requestBodyMd5 = toMD5String(requestBody); } String str2 = "23082404151001" + "POST" + f13443a + "/" + URLDecoder.decode("SmartInvestment%2FIndex%2FTianYan", "UTF-8") + timestampFixed + randomString + requestBodyMd5; String md5 = EncryptUtils.toMD5String(str2); log.info("Handmade emsign md5: {}", md5); String emSign = getEMSign(requestBody, "POST", "SmartInvestment%2FIndex%2FTianYan", randomString, timestampFixed); log.info("Method emsign: {}", emSign); // 测试密码加解密 String encryptPass = encryptAesForEmoneyPassword("123456"); log.info("Encrypt pass for 123456: {}", encryptPass); String decryptPass = decryptAesForEmoneyPassword(encryptPass); log.info("Decrypt pass for {}: {}", encryptPass, decryptPass); } }