1.背景
公司要求上传华为云的文件必须加密存放
2.相关文档
对接文档:https://support.huaweicloud.com/sdk-java-devg-obs/obs_21_0603.html
加密SDK:https://support.huaweicloud.com/sdk-java-devg-obs/obs_21_2303.html
官网示例:
import com.obs.services.crypto.CTRCipherGenerator;
import com.obs.services.crypto.CryptoObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.GetObjectRequest;
import com.obs.services.model.ObsObject;
import com.obs.services.model.PutObjectResult;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import org.junit.Test;
import org.m.util.MyUtil;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(classes = ObsServiceTest.class)
public class ObsServiceTest {
@Test
public void test() {
// 您可以通过环境变量获取访问密钥AK/SK,也可以使用其他外部引入方式传入。如果使用硬编码可能会存在泄露风险。
// 您可以登录访问管理控制台获取访问密钥AK/SK
// String ak = System.getenv("ACCESS_KEY_ID");
// String sk = System.getenv("SECRET_ACCESS_KEY_ID");
String ak = "RD2JV*****JB23";
String sk = "FC3JLpTgZc*******2rHxn9LPMtNOpLb4q";
// 【可选】如果使用临时AK/SK和SecurityToken访问OBS,同样建议您尽量避免使用硬编码,以降低信息泄露风险。
// 您可以通过环境变量获取访问密钥AK/SK/SecurityToken,也可以使用其他外部引入方式传入。
// String securityToken = System.getenv("SECURITY_TOKEN");
// endpoint填写桶所在的endpoint, 此处以华北-北京四为例,其他地区请按实际情况填写。
// String endPoint = "https://obs.cn-north-4.myhuaweicloud.com";
String endPoint = "https://obs.cn-bj1.ctyun.cn";
// 您可以通过环境变量获取endPoint,也可以使用其他外部引入方式传入。
//String endPoint = System.getenv("ENDPOINT");
CTRCipherGenerator ctrCipherGenerator = null;
try {
SecureRandom secureRandom = SecureRandom.getInstanceStrong();
// 注意 Linux 下SecureRandom.getInstanceStrong()在熵不够的情况下可能阻塞,建议同时增加相关的补熵机制,或者以其他方式设置SecureRandom
byte[] exampleMasterKey = new byte[CTRCipherGenerator.CRYPTO_KEY_BYTES_LEN];
secureRandom.nextBytes(exampleMasterKey);
// 这里的exampleMasterKey用随机生成的主密钥演示,使用时请您保证密钥长度为32,并妥善保管密钥。
ctrCipherGenerator =
// new CTRCipherGenerator("example_master_key_info", exampleMasterKey, true, secureRandom);
new CTRCipherGenerator("example_master_key_info", MyUtil.getMasterKey(), true, secureRandom);
} catch (IllegalArgumentException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
assert ctrCipherGenerator != null;
// 创建ObsClient实例
try (CryptoObsClient cryptoObsClient =
new CryptoObsClient(ak, sk, /*securityToken, */endPoint, ctrCipherGenerator)) {
// String exampleBucketName = "example-bucket";
// String exampleObjectKey = "exampleObjectKey";
String exampleBucketName = "uy4uw3qyg3";
// String exampleObjectKey = "dev/rrr.png";
String exampleObjectKey = "dev/12409030000072/36673_302_ac3f6c5793b1.png";
String examplePlainTextFilePath = "C:\\Users\\Administrator\\Downloads\\rrr.png";
String exampleDecryptedFilePath = "C:\\Users\\Administrator\\Downloads\\36673_302_ac3f6c5793b1.png";
// PutObjectResult putObjectResult =
// cryptoObsClient.putObject(exampleBucketName, exampleObjectKey, new File(examplePlainTextFilePath));
// System.out.println("HTTP Code: " + putObjectResult.getStatusCode());
// System.out.println("Etag: " + putObjectResult.getEtag());
// 客户端加密上传成功
System.out.println("CTRCipherGeneratorDemo001 putObject successfully");
GetObjectRequest getObjectRequest = new GetObjectRequest(exampleBucketName, exampleObjectKey);
ObsObject obsObject = cryptoObsClient.getObject(getObjectRequest);
InputStream input = obsObject.getObjectContent();
byte[] b = new byte[1024];
FileOutputStream fileOutputStream = new FileOutputStream(exampleDecryptedFilePath);
int len;
while ((len = input.read(b)) != -1) {
fileOutputStream.write(b, 0, len);
}
fileOutputStream.close();
input.close();
System.out.println("HTTP Code: " + obsObject.getMetadata().getStatusCode());
// 客户端解密下载成功
System.out.println("CTRCipherGeneratorDemo001 getObject successfully");
// 验证加密之前的文件和解密之后的文件是否一致
byte[] plainTextFileSha256 = CTRCipherGenerator.getFileSha256Bytes(examplePlainTextFilePath);
byte[] decryptedFileSha256 = CTRCipherGenerator.getFileSha256Bytes(exampleDecryptedFilePath);
String plainTextFileSha256Base64Encoded = CTRCipherGenerator.getBase64Info(plainTextFileSha256);
String decryptedFileSha256Base64Encoded = CTRCipherGenerator.getBase64Info(decryptedFileSha256);
System.out.println("plainTextFileSha256 base64 encoded: " + plainTextFileSha256Base64Encoded);
System.out.println("decryptedFileSha256 base64 encoded: " + decryptedFileSha256Base64Encoded);
System.out.println(
"plainTextFileSha256 equals decryptedFileSha256 ? "
+ decryptedFileSha256Base64Encoded.equals(plainTextFileSha256Base64Encoded));
System.out.println("CTRCipherGeneratorDemo001 successfully");
} catch (ObsException e) {
System.out.println("CTRCipherGeneratorDemo001 failed");
// 请求失败,打印http状态码
System.out.println("HTTP Code: " + e.getResponseCode());
// 请求失败,打印服务端错误码
System.out.println("Error Code:" + e.getErrorCode());
// 请求失败,打印详细错误信息
System.out.println("Error Message: " + e.getErrorMessage());
// 请求失败,打印请求id
System.out.println("Request ID:" + e.getErrorRequestId());
System.out.println("Host ID:" + e.getErrorHostId());
} catch (Exception e) {
System.out.println("CTRCipherGeneratorDemo001 putObject failed");
// 其他异常信息打印
e.printStackTrace();
}
}
}
3.对接实战
3.1 引入依赖
<!-- 华为云存储 -->
<dependency>
<groupId>com.huaweicloud</groupId>
<artifactId>esdk-obs-java</artifactId>
<version>3.24.8</version>
</dependency>
3.2 配置文件
hwyun: # 华为云OBS配置信息
obs:
accessKey: RD2J*********B23
securityKey: FC3***********LPMtNOpLb4q
endPoint: obs.cn-bj1.ctyun.cn
bucketName: uy****qyg3
rootPath: dev/
3.3 上传下载service
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.List;
public interface HweiYunOBSService {
/**
* @Description 删除文件
* @param: objectKey 文件名
* @return: boolean 执行结果
*/
boolean delete(String objectKey);
/**
* @Description 批量删除文件
* @param: objectKeys 文件名集合
* @return: boolean 执行结果
*/
boolean delete(List<String> objectKeys);
/**
* @Description 上传文件
* @param: uploadFile 上传文件
* @param: objectKey 文件名称
* @return: java.lang.String url访问路径
*/
String fileUpload(MultipartFile uploadFile, String objectKey);
/**
* @Description 加密上传文件
* @param: uploadFile 上传文件
* @param: objectKey 文件名称
* @return: java.lang.String url访问路径
*/
String fileCryptoUpload(MultipartFile uploadFile, String objectKey);
/**
* @Description 文件下载
*/
InputStream fileDownload(String objectKey);
/**
* @Description 文件解密下载
*/
InputStream fileCryptoDownload(String objectKey);
/**
* @Description 生成ObjectKey
*/
String generateObjectKey(String fileName);
}
3.4 实现类
import com.obs.services.ObsClient;
import com.obs.services.crypto.CryptoObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.*;
import lombok.extern.slf4j.Slf4j;
import org.mkk.boot.commontypemodeltool.exception.MKException;
import org.mkk.hpcvc.obs.properties.HweiOBSProperties;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
@Slf4j
public class HweiYunOBSServiceImpl implements HweiYunOBSService {
private ObsClientService obsClientService;
private HweiOBSProperties hweiOBSProperties;
public HweiYunOBSServiceImpl(ObsClientService obsClientService, HweiOBSProperties hweiOBSProperties) {
this.obsClientService = obsClientService;
this.hweiOBSProperties = hweiOBSProperties;
}
@Override
public boolean delete(String objectKey) {
ObsClient obsClient = null;
try {
// 创建ObsClient实例
obsClient = obsClientService.getInstance();
// obs删除
obsClient.deleteObject(hweiOBSProperties.getBucketName(), objectKey);
} catch (ObsException e) {
log.error("obs删除保存失败", e);
} finally {
obsClientService.destroy(obsClient);
}
return true;
}
@Override
public boolean delete(List<String> objectKeys) {
ObsClient obsClient = null;
try {
obsClient = obsClientService.getInstance();
DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(hweiOBSProperties.getBucketName());
objectKeys.forEach(x -> deleteObjectsRequest.addKeyAndVersion(x));
// 批量删除请求
obsClient.deleteObjects(deleteObjectsRequest);
return true;
} catch (ObsException e) {
log.error("obs删除保存失败", e);
} finally {
obsClientService.destroy(obsClient);
}
return false;
}
@Override
public String fileUpload(MultipartFile uploadFile, String objectKey) {
ObsClient obsClient = null;
try {
String bucketName = hweiOBSProperties.getBucketName();
obsClient = obsClientService.getInstance();
// 判断桶是否存在
boolean exists = obsClient.headBucket(bucketName);
if (!exists) {
// 若不存在,则创建桶
HeaderResponse response = obsClient.createBucket(bucketName);
log.info("创建桶成功" + response.getRequestId());
}
InputStream inputStream = uploadFile.getInputStream();
long available = inputStream.available();
PutObjectRequest request = new PutObjectRequest(bucketName, objectKey, inputStream);
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(available);
request.setMetadata(objectMetadata);
// 设置对象访问权限为公共读
request.setAcl(AccessControlList.REST_CANNED_PUBLIC_READ);
PutObjectResult result = obsClient.putObject(request);
// 读取该已上传对象的URL
log.info("已上传对象的URL" + result.getObjectUrl());
return result.getObjectUrl();
} catch (ObsException e) {
log.error("obs上传失败", e);
} catch (IOException e) {
log.error("上传失败", e);
} finally {
obsClientService.destroy(obsClient);
}
return null;
}
@Override
public String fileCryptoUpload(MultipartFile uploadFile, String objectKey) {
CryptoObsClient obsClient = null;
try {
String bucketName = hweiOBSProperties.getBucketName();
obsClient = obsClientService.getCryptoObsInstance();
// 判断桶是否存在
boolean exists = obsClient.headBucket(bucketName);
if (!exists) {
// 若不存在,则创建桶
HeaderResponse response = obsClient.createBucket(bucketName);
log.info("创建桶成功" + response.getRequestId());
}
InputStream inputStream = uploadFile.getInputStream();
long available = inputStream.available();
PutObjectRequest request = new PutObjectRequest(bucketName, objectKey, inputStream);
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(available);
request.setMetadata(objectMetadata);
// 设置对象访问权限为公共读
request.setAcl(AccessControlList.REST_CANNED_PUBLIC_READ);
PutObjectResult result = obsClient.putObject(request);
// 读取该已上传对象的URL
log.info("已上传对象的URL" + result.getObjectUrl());
return result.getObjectUrl();
} catch (ObsException e) {
log.error("obs上传失败", e);
} catch (IOException e) {
log.error("上传失败", e);
} finally {
obsClientService.destroy(obsClient);
}
return null;
}
@Override
public InputStream fileDownload(String objectKey) {
ObsClient obsClient = null;
try {
String bucketName = hweiOBSProperties.getBucketName();
obsClient = obsClientService.getInstance();
ObsObject obsObject = obsClient.getObject(bucketName, objectKey);
return obsObject.getObjectContent();
} catch (Exception e) {
log.error("obs文件下载失败", e);
throw new MKException("obs文件下载失败");
} finally {
obsClientService.destroy(obsClient);
}
}
@Override
public InputStream fileCryptoDownload(String objectKey) {
CryptoObsClient obsClient = null;
try {
String bucketName = hweiOBSProperties.getBucketName();
obsClient = obsClientService.getCryptoObsInstance();
GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, objectKey);
ObsObject obsObject = obsClient.getObject(getObjectRequest);
return obsObject.getObjectContent();
} catch (Exception e) {
log.error("obs文件下载失败", e);
throw new MKException("obs文件下载失败");
} finally {
obsClientService.destroy(obsClient);
}
}
@Override
public String generateObjectKey(String fileName) {
return hweiOBSProperties.getRootPath() + fileName;
}
}
3.5 获取Client服务
import com.obs.services.crypto.CTRCipherGenerator;
import com.obs.services.crypto.CryptoObsClient;
import com.obs.services.exception.ObsException;
import lombok.extern.slf4j.Slf4j;
import org.mkk.boot.commontypemodeltool.exception.MKException;
import org.mkk.hpcvc.obs.properties.HweiOBSProperties;
import org.mkk.hpcvc.util.MyUtil;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
* ObsClient 类。
*
* @author songjn
* @date 2024-09-05 [songjn] 创建。
*/
@Slf4j
public class ObsClientService {
private HweiOBSProperties hweiOBSProperties;
public ObsClientService(HweiOBSProperties hweiOBSProperties) {
this.hweiOBSProperties = hweiOBSProperties;
}
/**
* 获取OBS客户端实例
*/
public com.obs.services.ObsClient getInstance() {
return new com.obs.services.ObsClient(hweiOBSProperties.getAccessKey(), hweiOBSProperties.getSecurityKey(), hweiOBSProperties.getEndPoint());
}
/**
* 获取OBS加密客户端实例
*/
public CryptoObsClient getCryptoObsInstance() {
CTRCipherGenerator ctrCipherGenerator = null;
try {
ctrCipherGenerator =
new CTRCipherGenerator("hpcvc", MyUtil.getMasterKey(), true, SecureRandom.getInstanceStrong());
} catch (NoSuchAlgorithmException e) {
log.error("获取OBS加密客户端实例失败,NoSuchAlgorithmException", e);
throw new MKException("获取OBS加密客户端实例失败");
}
CryptoObsClient cryptoObsClient =
new CryptoObsClient(hweiOBSProperties.getAccessKey(),
hweiOBSProperties.getSecurityKey(),
hweiOBSProperties.getEndPoint(), ctrCipherGenerator);
return cryptoObsClient;
}
/**
* 销毁OBS客户端实例
*/
public void destroy(com.obs.services.ObsClient obsClient) {
try {
obsClient.close();
} catch (ObsException e) {
log.error("obs执行失败", e);
} catch (Exception e) {
log.error("执行失败", e);
}
}
}
3.6 读取配置properties
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Setter
@Getter
@ConfigurationProperties(prefix = HweiOBSProperties.PREFIX)
public class HweiOBSProperties {
public static final String PREFIX = "hwyun.obs";
/**
* 访问密钥AK
*/
private String accessKey;
/**
* 访问密钥SK
*/
private String securityKey;
/**
* 终端节点
*/
private String endPoint;
/**
* 桶
*/
private String bucketName;
/**
* 存储根路径
*/
private String rootPath;
}
3.7 config配置文件
import lombok.extern.slf4j.Slf4j;
import org.mkk.hpcvc.obs.HweiYunOBSService;
import org.mkk.hpcvc.obs.HweiYunOBSServiceImpl;
import org.mkk.hpcvc.obs.ObsClientService;
import org.mkk.hpcvc.obs.properties.HweiOBSProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
@ConditionalOnProperty(prefix = HweiOBSProperties.PREFIX, name = "accessKey")
@EnableConfigurationProperties(HweiOBSProperties.class)
public class HweiOBSConfig {
@Bean
public ObsClientService obsClientService(HweiOBSProperties hweiOBSProperties) {
return new ObsClientService(hweiOBSProperties);
}
@Bean
public HweiYunOBSService hweiYunOBSService(ObsClientService obsClientService, HweiOBSProperties hweiOBSProperties) {
return new HweiYunOBSServiceImpl(obsClientService, hweiOBSProperties);
}
}
META-INF中spring.factorys中添加配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mkk.hpcvc.obs.config.HweiOBSConfig
3.8 工具类获取密钥
/**
* 获取MasterKey
*/
public static byte[] getMasterKey() {
return Base64.getDecoder().decode(CommonConstant.OBS_SECRET);
}
/**
* 长度32的密钥转换
*/
private void testMasterKey(String encodedText) {
byte[] exampleMasterKey = new byte[32];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(exampleMasterKey);
String keyString = Base64.getEncoder().encodeToString(exampleMasterKey);
System.out.println("Generated Key String: " + keyString);
byte[] decodedKey = Base64.getDecoder().decode(keyString);
boolean isSame = java.util.Arrays.equals(exampleMasterKey, decodedKey);
System.out.println("Is decoded key same as original? " + isSame);
}