Skip to content

Commit

Permalink
feat: 敏感信息存储支持国密 TencentBlueKing#2055
Browse files Browse the repository at this point in the history
升级SDK版本,导入导出相关场景改造
  • Loading branch information
jsonwan committed Jul 25, 2023
1 parent d57161b commit 55b5ddb
Show file tree
Hide file tree
Showing 39 changed files with 1,090 additions and 766 deletions.
2 changes: 1 addition & 1 deletion src/backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ ext {
set('jcommanderVersion', "1.71")
set('kubernetesJavaClientVersion', "11.0.4")
set('springCloudKubernetesVersion', "2.0.6")
set('cryptoJavaSDKVersion', "0.0.7-SNAPSHOT")
set('cryptoJavaSDKVersion', "1.0.0")
if (System.getProperty("bkjobVersion")) {
set('bkjobVersion', System.getProperty("bkjobVersion"))
println "bkjobVersion:" + bkjobVersion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package com.tencent.bk.job.common.util.crypto;

import com.tencent.bk.job.common.util.Base64Util;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
Expand All @@ -33,11 +34,15 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.Arrays;

@Slf4j
public class AESUtils {
/**
* 加密/解密算法/工作模式/填充方式
Expand Down Expand Up @@ -187,33 +192,43 @@ private static SecretKeySpec getKeySpec(Cipher cipher, byte[] key)
}

public static void encrypt(File inFile, File outFile, String password) throws Exception {
try (FileInputStream in = new FileInputStream(inFile); FileOutputStream out = new FileOutputStream(outFile)) {
encrypt(in, out, password);
}
}

public static void encrypt(InputStream in, OutputStream out, String password) throws Exception {
byte[] key = password.getBytes(StandardCharsets.UTF_8);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(cipher, key));
try (FileInputStream in = new FileInputStream(inFile); FileOutputStream out = new FileOutputStream(outFile)) {
byte[] arr = cipher.getIV();
if (arr == null) {
throw new RuntimeException(String.format("CIPHER_ALGORITHM %s is invalid", CIPHER_ALGORITHM));
}
out.write(arr);
write(in, out, cipher);
byte[] arr = cipher.getIV();
if (arr == null) {
throw new RuntimeException(String.format("CIPHER_ALGORITHM %s is invalid", CIPHER_ALGORITHM));
}
out.write(arr);
write(in, out, cipher);
}

public static void decrypt(File inFile, File outFile, String password) throws Exception {
try (FileInputStream in = new FileInputStream(inFile); FileOutputStream out = new FileOutputStream(outFile)) {
decrypt(in, out, password);
}
}

public static void decrypt(InputStream in, OutputStream out, String password) throws Exception {
log.debug("decrypt: in.available={}", in.available());
byte[] key = password.getBytes(StandardCharsets.UTF_8);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
try (FileInputStream in = new FileInputStream(inFile); FileOutputStream out = new FileOutputStream(outFile)) {
byte[] iv = new byte[cipher.getBlockSize()];
if (in.read(iv) < iv.length) {
throw new RuntimeException();
}
cipher.init(Cipher.DECRYPT_MODE, getKeySpec(cipher, key), new IvParameterSpec(iv));
write(in, out, cipher);
byte[] iv = new byte[cipher.getBlockSize()];
if (in.read(iv) < iv.length) {
throw new RuntimeException();
}
log.debug("decrypt: iv={}", Arrays.toString(iv));
cipher.init(Cipher.DECRYPT_MODE, getKeySpec(cipher, key), new IvParameterSpec(iv));
write(in, out, cipher);
}

private static void write(FileInputStream in, FileOutputStream out, Cipher cipher) throws Exception {
private static void write(InputStream in, OutputStream out, Cipher cipher) throws Exception {
byte[] iBuffer = new byte[1024];
int len;
while ((len = in.read(iBuffer)) != -1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
import org.slf4j.helpers.FormattingTuple;
import org.slf4j.helpers.MessageFormatter;

import java.io.InputStream;
import java.io.OutputStream;

/**
* 使用AES/CBC/PKCS5Padding的加密实现
*/
Expand All @@ -48,10 +51,13 @@ public byte[] encryptIndeed(byte[] key, byte[] message) {
try {
return AESUtils.encrypt(message, key);
} catch (Exception e) {
FormattingTuple msg = MessageFormatter.format(
"Fail to encrypt using AES_CBC, key.len={}, message.len={}",
key.length,
message.length
FormattingTuple msg = MessageFormatter.arrayFormat(
"Fail to encrypt using {}, key.len={}, message.len={}",
new Object[]{
getName(),
key.length,
message.length
}
);
throw new CryptoException(msg.getMessage(), e);
}
Expand All @@ -62,12 +68,47 @@ public byte[] decryptIndeed(byte[] key, byte[] encryptedMessage) {
try {
return AESUtils.decrypt(encryptedMessage, key);
} catch (Exception e) {
FormattingTuple msg = MessageFormatter.format(
"Fail to decrypt using AES_CBC, key.len={}, encryptedMessage.len={}",
key.length,
encryptedMessage.length
FormattingTuple msg = MessageFormatter.arrayFormat(
"Fail to decrypt using {}, key.len={}, encryptedMessage.len={}",
new Object[]{
getName(),
key.length,
encryptedMessage.length
}
);
throw new CryptoException(msg.getMessage(), e);
}
}

@Override
public void encryptIndeed(String key, InputStream in, OutputStream out) {
try {
AESUtils.encrypt(in, out, key);
} catch (Exception e) {
FormattingTuple msg = MessageFormatter.arrayFormat(
"Fail to encrypt using {}, key.len={}",
new Object[]{
getName(),
key.length()
}
);
throw new com.tencent.bk.sdk.crypto.exception.CryptoException(msg.getMessage(), e);
}
}

@Override
public void decryptIndeed(String key, InputStream in, OutputStream out) {
try {
AESUtils.decrypt(in, out, key);
} catch (Exception e) {
FormattingTuple msg = MessageFormatter.arrayFormat(
"Fail to decrypt using {}, key.len={}",
new Object[]{
getName(),
key.length()
}
);
throw new com.tencent.bk.sdk.crypto.exception.CryptoException(msg.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ public enum CryptoScenarioEnum {
// DB账号的密码
DATABASE_PASSWORD((byte) 0, "databasePassword"),
// 凭证信息
CREDENTIAL((byte) 0, "credential");
CREDENTIAL((byte) 0, "credential"),
// 导出作业的密码
EXPORT_JOB_PASSWORD((byte) 0, "exportJobPassword"),
// 导出作业的备份文件
BACKUP_FILE((byte) 0, "backupFile");

// 加密类型:0为对称加密,1为非对称加密
private final byte type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,19 @@

package com.tencent.bk.job.common.encrypt;

import com.tencent.bk.sdk.crypto.cryptor.CryptorMetaDefinition;
import com.tencent.bk.sdk.crypto.cryptor.SymmetricCryptor;
import com.tencent.bk.sdk.crypto.cryptor.SymmetricCryptorFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -51,13 +57,90 @@ public SymmetricCryptoService(CryptoConfigService cryptoConfigService) {
}

/**
* 根据场景获取加密算法
* 从密文的前缀元数据中解析出使用的加密算法名称
*
* @param cipher 密文
* @return 加密算法名称,如果密文不包含指定前缀的元数据则返回null
*/
public String getAlgorithmFromCipher(String cipher) {
String prefix = CryptorMetaDefinition.getCipherMetaPrefix();
if (cipher.startsWith(prefix)) {
int indexOfPrefixLastChar = cipher.indexOf(CryptorMetaDefinition.getCipherMetaSuffix());
if (indexOfPrefixLastChar < 0) {
return null;
}
return cipher.substring(prefix.length(), indexOfPrefixLastChar);
}
return null;
}

/**
* 从密文的前缀元数据中解析出使用的加密算法名称
*
* @param cipherIns 密文输入流
* @return 加密算法名称,如果密文不包含指定前缀的元数据则返回null
*/
public String getAlgorithmFromCipherStream(BufferedInputStream cipherIns) {
String prefix = CryptorMetaDefinition.getCipherMetaPrefix();
String suffix = CryptorMetaDefinition.getCipherMetaSuffix();
int algorithmMaxLength = 100;
int cipherMetaMaxLength = prefix.length() + suffix.length() + algorithmMaxLength;
cipherIns.mark(cipherMetaMaxLength);
byte[] realPrefixBytes = new byte[prefix.length()];
try {
int n = cipherIns.read(realPrefixBytes);
if (n < prefix.length()) {
log.info("Cannot find enough cipherMetaPrefix bytes: expected={}, actually={}", prefix.length(), n);
return null;
}
if (!Arrays.equals(realPrefixBytes, prefix.getBytes())) {
log.info(
"Cannot find cipherMetaPrefix: expected={}, actually={}",
Arrays.toString(prefix.getBytes()),
Arrays.toString(realPrefixBytes)
);
return null;
}
byte[] algorithmWithSuffixBytes = new byte[algorithmMaxLength + suffix.length()];
n = cipherIns.read(algorithmWithSuffixBytes);
String algorithmWithSuffix = new String(algorithmWithSuffixBytes);
int indexOfSuffix = algorithmWithSuffix.indexOf(suffix);
if (indexOfSuffix == -1) {
log.info(
"Cannot find cipherMetaSuffix: algorithmWithSuffixBytes={}, suffixBytes={}",
Arrays.toString(algorithmWithSuffixBytes),
suffix.getBytes()
);
return null;
}
return algorithmWithSuffix.substring(0, indexOfSuffix);
} catch (Exception e) {
log.warn("Fail to read cipherMetaPrefix from cipherIns", e);
return null;
} finally {
try {
cipherIns.reset();
} catch (IOException e) {
log.error("Fail to reset cipherIns", e);
}
}
}

/**
* 对流数据加密,加密后的数据写入输出流中
*
* @param key 密钥
* @param in 输入流
* @param out 输出流
* @param cryptoScenarioEnum 加密场景
* @return 使用的加密算法标识
*/
public String getAlgorithmByScenario(CryptoScenarioEnum cryptoScenarioEnum) {
return cryptoConfigService.getSymmetricAlgorithmByScenario(cryptoScenarioEnum);
public void encrypt(String key,
InputStream in,
OutputStream out,
CryptoScenarioEnum cryptoScenarioEnum) {
String algorithm = cryptoConfigService.getSymmetricAlgorithmByScenario(cryptoScenarioEnum);
SymmetricCryptor cryptor = cryptorMap.computeIfAbsent(algorithm, SymmetricCryptorFactory::getCryptor);
cryptor.encrypt(key, in, out);
}

/**
Expand Down Expand Up @@ -88,14 +171,19 @@ public String encryptToBase64Str(String message, String algorithm) {
}

/**
* 对Base64编码的加密后的密文信息解密,返回解密后的明文
* 对流数据解密,解密后的数据写入输出流中
*
* @param base64EncryptedMessage Base64编码的加密后的密文信息,不可为空
* @param cryptoScenarioEnum 加密场景
* @return 解密后的明文信息
* @param key 密钥
* @param in 输入流
* @param out 输出流
* @param algorithm 解密算法
*/
public String decrypt(String base64EncryptedMessage, CryptoScenarioEnum cryptoScenarioEnum) {
return decrypt(base64EncryptedMessage, cryptoConfigService.getSymmetricAlgorithmByScenario(cryptoScenarioEnum));
public void decrypt(String key,
BufferedInputStream in,
OutputStream out,
String algorithm) {
SymmetricCryptor cryptor = cryptorMap.computeIfAbsent(algorithm, SymmetricCryptorFactory::getCryptor);
cryptor.decrypt(key, in, out);
}

/**
Expand Down
Loading

0 comments on commit 55b5ddb

Please sign in to comment.