/** * BladeX Commercial License Agreement * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. *
* Use of this software is governed by the Commercial License Agreement * obtained after purchasing a license from BladeX. *
* 1. This software is for development use only under a valid license * from BladeX. *
* 2. Redistribution of this software's source code to any third party * without a commercial license is strictly prohibited. *
* 3. Licensees may copyright their own code but cannot use segments * from this software for such purposes. Copyright of this software * remains with BladeX. *
* Using this software signifies agreement to this License, and the software * must not be used for illegal purposes. *
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is * not liable for any claims arising from secondary or illegal development. *
* Author: Chill Zhuang (bladejava@qq.com) */ package org.sxkj.gateway.provider; import org.springblade.core.launch.props.BladeProperties; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Arrays; import java.util.Objects; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; /** * 超级密钥加解密工具类 * * @author BladeX */ public class KeyProvider { public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; /** * API Key 前缀 */ private static final String API_KEY_PREFIX = "ak-"; /** * API Key 配置项 */ private static final String BLADE_KEY_ENABLED = "blade.key.enabled"; /** * API Key 配置项 */ private static final String BLADE_KEY_CRYPTO_KEY = "blade.key.crypto-key"; /** * 随机字符串因子 */ private static final String RANDOM_FACTOR = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; /** * 安全随机数生成器 */ private static final SecureRandom SECURE_RANDOM = new SecureRandom(); /** * PKCS7 块大小 */ private static final int BLOCK_SIZE = 16; /** * Hex 编码字符 */ private static final byte[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; /** * 判断是否为合法 API Key * * @param auth 认证字符串 * @return 是否为 API Key */ public static boolean isApiKey(@Nullable String auth, BladeProperties bladeProperties) { // 检查功能是否启用 String enabled = bladeProperties.getEnvironment().getProperty(BLADE_KEY_ENABLED); if (!Boolean.parseBoolean(enabled)) { return false; } // 检查前缀并解析 if (auth != null && auth.startsWith(API_KEY_PREFIX)) { String cryptoKey = bladeProperties.getEnvironment().getProperty(BLADE_KEY_CRYPTO_KEY); String parsed = parseKey(auth, cryptoKey); return parsed != null; } return false; } /** * 生成 API Key * * @param cryptoKey 加密密钥 * @return 带前缀的加密 Key */ public static String generateKey(String cryptoKey) { return API_KEY_PREFIX + encryptToHex(randomUUID(), cryptoKey); } /** * 解析 API Key * * @param key 带前缀的加密 Key * @param cryptoKey 加密密钥 * @return 解密后的原始值 */ @Nullable public static String parseKey(@Nullable String key, String cryptoKey) { if (isBlank(key) || !key.startsWith(API_KEY_PREFIX)) { return null; } try { String encryptedPart = key.substring(API_KEY_PREFIX.length()); return decryptFormHexToString(encryptedPart, cryptoKey); } catch (Exception e) { return null; } } /** * 加密并转换为十六进制字符串 * * @param content 明文内容 * @param aesTextKey AES 密钥字符串 * @return 十六进制加密字符串 */ public static String encryptToHex(String content, String aesTextKey) { return hexEncode(encrypt(content.getBytes(DEFAULT_CHARSET), aesTextKey)); } /** * 从十六进制字符串解密并返回明文字符串 * * @param content 十六进制加密字符串 * @param aesTextKey AES 密钥字符串 * @return 解密后的明文字符串 */ @Nullable public static String decryptFormHexToString(@Nullable String content, String aesTextKey) { byte[] hexBytes = decryptFormHex(content, aesTextKey); if (hexBytes == null) { return null; } return new String(hexBytes, DEFAULT_CHARSET); } /** * 从十六进制字符串解密并返回字节数组 * * @param content 十六进制加密字符串 * @param aesTextKey AES 密钥字符串 * @return 解密后的字节数组 */ @Nullable public static byte[] decryptFormHex(@Nullable String content, String aesTextKey) { if (isBlank(content)) { return null; } return decrypt(hexDecode(content.getBytes(DEFAULT_CHARSET)), aesTextKey); } /** * 加密字节数组 * * @param content 明文字节数组 * @param aesTextKey AES 密钥字符串 * @return 加密后的字节数组 */ public static byte[] encrypt(byte[] content, String aesTextKey) { return aes(pkcs7Encode(content), Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET), Cipher.ENCRYPT_MODE); } /** * 解密字节数组 * * @param content 加密的字节数组 * @param aesTextKey AES 密钥字符串 * @return 解密后的字节数组 */ public static byte[] decrypt(byte[] content, String aesTextKey) { return pkcs7Decode(aes(content, Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET), Cipher.DECRYPT_MODE)); } /** * AES 加解密核心方法 */ private static byte[] aes(byte[] data, byte[] aesKey, int mode) { Assert.isTrue(aesKey.length == 32, "IllegalAesKey, aesKey's length must be 32"); try { Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); cipher.init(mode, keySpec, iv); return cipher.doFinal(data); } catch (Exception e) { throw new RuntimeException(e); } } // ==================== 内联工具方法 ==================== /** * 生成指定长度的随机字符串 */ public static String randomKey(int count) { char[] buffer = new char[count]; for (int i = 0; i < count; i++) { buffer[i] = RANDOM_FACTOR.charAt(SECURE_RANDOM.nextInt(RANDOM_FACTOR.length())); } return new String(buffer); } /** * 生成无横线的 UUID */ private static String randomUUID() { ThreadLocalRandom random = ThreadLocalRandom.current(); return new UUID(random.nextLong(), random.nextLong()).toString().replace("-", ""); } /** * 检查字符串是否为空或空白 */ private static boolean isBlank(@Nullable String str) { if (str == null || str.isEmpty()) { return true; } for (int i = 0; i < str.length(); i++) { if (!Character.isWhitespace(str.charAt(i))) { return false; } } return true; } /** * 字节数组转十六进制字符串 */ private static String hexEncode(byte[] data) { int len = data.length; byte[] out = new byte[len << 1]; for (int i = 0, j = 0; i < len; i++) { out[j++] = HEX_DIGITS[(0xF0 & data[i]) >>> 4]; out[j++] = HEX_DIGITS[0x0F & data[i]]; } return new String(out, DEFAULT_CHARSET); } /** * 十六进制字节数组解码为原始字节数组 */ private static byte[] hexDecode(byte[] data) { int len = data.length; if ((len & 0x01) != 0) { throw new IllegalArgumentException("hexBinary needs to be even-length: " + len); } byte[] out = new byte[len >> 1]; for (int i = 0, j = 0; j < len; i++) { int f = Character.digit(data[j++], 16) << 4; f |= Character.digit(data[j++], 16); out[i] = (byte) (f & 0xFF); } return out; } /** * PKCS7 编码(填充) */ private static byte[] pkcs7Encode(byte[] src) { int count = src.length; int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); byte pad = (byte) (amountToPad & 0xFF); byte[] dest = new byte[count + amountToPad]; System.arraycopy(src, 0, dest, 0, count); Arrays.fill(dest, count, dest.length, pad); return dest; } /** * PKCS7 解码(去填充) */ private static byte[] pkcs7Decode(byte[] decrypted) { int pad = decrypted[decrypted.length - 1]; if (pad < 1 || pad > BLOCK_SIZE) { pad = 0; } if (pad > 0) { return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); } return decrypted; } }