文章目录
- 1. 项目背景
- 2. xdr设备实例信息
- 1. xdr实例信息-XdrInstanceDto
- 2. xdr特定的配置信息-XdrConfig
- 3. xdr接入方式-XdrConfigTypeEnum
- 2. 解密联动码获取AkSk信息
- 1. 生成联动码code的加密和解密密钥
- 2. AES加密算法: 对联动码code加密
- 3. AES解密算法: 对联动码code解密
1. 项目背景
① 概念:
- openApi:所有对外对接提供的API接口定义为openApi,openApi走AkSk认证,无需 access_token。
- AK(Access Key):设备的唯一标识;
- SK(Secret Key): 秘钥,和AK成对出现;
② 原理:
- 首先通信双方持有相同的 AKSK 信息;
- 调用接口前,调用方通过AK找到SK信息,用SK对请求的接口的请求方式、地址、参数、请求头进行签名,得到签名串;
- 调用方在请求头放入AK和签名串;
- 被调用方从请求头取出AK,并通过AK找到SK信息,用SK以同样的方式进行签名;
- 被调用方用签名结果和请求头中的签名串进行比对,签名一致,认证通过;
③ AkSk信息需要从xdr设备联动码code中解密获得:
- 获取xdr设备实例中的设备名称(用户名username)和设备密码(密码password)
- 生成对称加密算法AES-256中加密和解密的密钥;
- 使用对称加密算法的密钥对xdr设备实例中的联动码code进行解密获取AkSk信息;
2. xdr设备实例信息
1. xdr实例信息-XdrInstanceDto
@Data
public class XdrInstanceDto {
@ApiModelProperty("xdr实例名称")
private String name;
@ApiModelProperty("xdr实例id")
private String instId;
@ApiModelProperty("是否启用")
private Boolean enable;
@ApiModelProperty("xdr特定的配置信息")
private XdrConfig config;
}
2. xdr特定的配置信息-XdrConfig
@Data
public class XdrConfig {
@ApiModelProperty("接入方式")
@Enum(clazz = XdrConfigTypeEnum.class, method = "getType", message = "app.instance.config.xdr.type.not.valid")
private Integer type;
@Size(min = 1, max = 1024, message = "app.instance.config.code.size.limit")
@ApiModelProperty("用于获取设备唯一标识的联通码")
private String code;
@ApiModelProperty("解密AK所需的用户名")
@Pattern(regexp = "^\\w{1,70}$", message = "app.instance.config.username.not.valid")
private String username;
@ApiModelProperty("解密AK所需的密码")
@Size(min = 1, max = 90, message = "app.instance.config.password.not.valid")
private String password;
@ApiModelProperty("企业id")
@Pattern(regexp = "^\\d{1,16}$", message = "app.instance.config.company.id.not.valid")
private String companyId;
@ApiModelProperty("云图地址")
@Pattern(regexp = "^[A-Za-z\\d.]*$", message = "app.instance.config.cloudAddress.not.valid")
private String cloudAddress;
@ApiModelProperty("联动总线地址")
@Pattern(regexp = "^(http://|https://)[A-Za-z\\d.:]*$", message = "app.instance.config.auth.address.not.valid")
private String linkageBusAddress;
}
3. xdr接入方式-XdrConfigTypeEnum
@Getter
@AllArgsConstructor
public enum XdrConfigTypeEnum {
/**
* 分布式
*/
LOCAL(1),
/**
* SAAS
*/
SAAS(0);
/**
* 接入类型
*/
final Integer type;
}
2. 解密联动码获取AkSk信息
- 获取xdr设备实例中的设备名称(用户名username)和设备密码(密码password)
- 生成对称加密算法AES-256中加密和解密的密钥;
- 使用对称加密算法的密钥对xdr设备实例中的联动码code进行解密获取AkSk信息;
/**
* 根据实例信息生成AkSk信息:解密联动码获取AKSK信息
*
* @param xdrInstanceDto 连接实例
* @return AkSk信息
*/
@Nullable
private AkSkInfo generateAkSkInfo(XdrInstanceDto xdrInstanceDto) {
try {
// 1、生成对称加密和解密密钥
String key = AesGcmEncrypt.generateLinkageCodeKey(
xdrInstanceDto.getConfig().getUsername(),
xdrInstanceDto.getConfig().getPassword()
);
// 2、使用密钥对联动码code解密获的AkSk信息
String akskJsonStr = AesEcbEncrypt.decryptAes(
xdrInstanceDto.getConfig().getCode(),
key
);
// 3、将akskJsonStr反序列化为AkSkInfo对象
@Nullable
AkSkInfo akskInfo = JsonTranscodeUtil.transcode(
akskJsonStr,
AkSkInfo.class
);
return akskInfo;
} catch (Exception e) {
log.error("decrypt linkage code failed, code: {}", xdrInstanceDto.getConfig().getCode(), e);
throw new DeviceConnectException("exception.xdr.device.connect.failed");
}
}
1. 生成联动码code的加密和解密密钥
/**
* AES 加解密联动码code
*/
public class AesGcmEncrypt {
private static final int CUT_POINT = 2;
/**
* 生成联动码解密密钥
*
* @param deviceName 设备名称
* @param devicePwd 设备密码
* @return 加密密钥
*/
public static String generateLinkageCodeKey(String deviceName, String devicePwd) {
if (StringUtils.isEmpty(deviceName) || deviceName.length() < CUT_POINT ||
StringUtils.isEmpty(devicePwd) || devicePwd.length() < CUT_POINT) {
return "";
}
// 根据deviceName和devicePwd生成一个key
String key = deviceName.substring(0, CUT_POINT)
.concat(devicePwd.substring(0, CUT_POINT))
.concat(deviceName.substring(CUT_POINT))
.concat(devicePwd.substring(CUT_POINT));
// 生成密钥
return DigestUtils.sha256Hex(key).substring(0, 32);
}
}
2. AES加密算法: 对联动码code加密
/**
* AES 加解密 解密联动码
*/
public class AesGcmEncrypt {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String ALGORITHM = "AES";
private static final String PADDING_TYPE = "AES/GCM/PKCS5Padding";
private static final int GCM_IV_SIZE = 16;
private static final int GCM_TAG_LENGTH = 16;
private static final String CHARSET_NAME = "UTF-8";
private static final int CUT_POINT = 2;
/**
* AES加密
*
* @param content 将要加密的内容
* @param key 密钥
* @return 已经加密的字节数组内容转为Base64之后的字符串
*/
public static String encryptAes(String content, String key)
throws UnsupportedEncodingException, NoSuchPaddingException, NoSuchAlgorithmException,
IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
byte[] keyByte = key.getBytes(CHARSET_NAME);
SecretKeySpec keySpec = new SecretKeySpec(keyByte, ALGORITHM);
Cipher cipher = Cipher.getInstance(PADDING_TYPE);
byte[] iv = new byte[GCM_IV_SIZE];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(iv);
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * Byte.SIZE, iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
byte[] resultByte = cipher.doFinal(content.getBytes(CHARSET_NAME));
byte[] encrypted = new byte[iv.length + resultByte.length];
System.arraycopy(iv, 0, encrypted, 0, iv.length);
System.arraycopy(resultByte, 0, encrypted, iv.length, resultByte.length);
return Base64Utils.encodeToString(encrypted);
}
}
3. AES解密算法: 对联动码code解密
/**
* AES 加解密 解密联动码
*/
public class AesGcmEncrypt {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String ALGORITHM = "AES";
private static final String PADDING_TYPE = "AES/GCM/PKCS5Padding";
private static final int GCM_IV_SIZE = 16;
private static final int GCM_TAG_LENGTH = 16;
private static final String CHARSET_NAME = "UTF-8";
private static final int CUT_POINT = 2;
/**
* AES解密
*
* @param content 将要解密的字节数组内容的base64字符串
* @param key 密钥
* @return 已经解密的内容
*/
public static String decryptAes(String content, String key)
throws UnsupportedEncodingException, NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
byte[] data = Base64Utils.decodeFromString(content);
byte[] keyByte = key.getBytes(CHARSET_NAME);
SecretKeySpec keySpec = new SecretKeySpec(keyByte, ALGORITHM);
byte[] iv = Arrays.copyOfRange(data, 0, GCM_IV_SIZE);
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * Byte.SIZE, iv);
Cipher cipher = Cipher.getInstance(PADDING_TYPE);
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
byte[] decryptData = cipher.doFinal(data, GCM_IV_SIZE, data.length - GCM_IV_SIZE);
String resultStr = new String(decryptData);
return resultStr.trim();
}
}