签名
为防止request中的json在传输过程中被更改,
- 需要在传送双方保存一个字符串sinature-key
- 用SHA256 hash请求中的json字符串,结果为hash1
- {"payload":hash1}以此为字符和sinature-key用JWS HS256算法进行签名,得到sinature1
- 在请求的json中加入字段:"sinature":sinature1
- 接收方进行逆过程,验证是否会得到该字符串sinature-key
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.text.ParseException;
import java.util.Base64;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
public final class SignatureUtil {
private SignatureUtil() {}
public static byte[] genSignatureKey() {
// Generate random 256-bit (32-byte) shared secret
SecureRandom random = new SecureRandom();
byte[] sharedSecret = new byte[32];
random.nextBytes(sharedSecret);
// a0a2abd8-6162-41c3-83d6-1cf559b46afc
return sharedSecret;
}
public static String genSignatureKeyString() {
return Base64.getEncoder().encodeToString(genSignatureKey());
}
public static byte[] decodeSignatureKey(String signatureKey) {
return Base64.getDecoder().decode(signatureKey);
}
/**
* P.147
* sign app_body json string
*
* @param content
* @param signatureKey
* @return
* @throws JOSEException
*/
public static String sign(String content, byte[] signatureKey) throws JOSEException {
// Create HMAC signer
JWSSigner signer = new MACSigner(signatureKey);
// Prepare JWS object with "Hello, world!" payload
JWSObject jwsObject = new JWSObject(new JWSHeader(JWSAlgorithm.HS256), new Payload(content));
// Apply the HMAC
jwsObject.sign(signer);
// To serialize to compact form, produces something like
// eyJhbGciOiJIUzI1NiJ9.SGVsbG8sIHdvcmxkIQ.onO9Ihudz3WkiauDO2Uhyuz0Y18UASXlSc1eS0NkWyA
return jwsObject.serialize();
}
public static boolean verify(String content, byte[] signatureKey) {
try {
verifyAndGet(content, signatureKey);
return true;
} catch (Exception e){
return false;
}
}
public static String verifyAndGet(String content, byte[] signatureKey) throws ParseException, JOSEException {
// To parse the JWS and verify it, e.g. on client-side
JWSObject jwsObject = JWSObject.parse(content);
JWSVerifier verifier = new MACVerifier(signatureKey);
jwsObject.verify(verifier);
return jwsObject.getPayload().toString();
}
public static void main(String [] args) throws JOSEException {
String content = "Hello, world!";
String signatureKey = "a0a2abd8-6162-41c3-83d6-1cf559b46afc";
byte[] signatureKeyByte = signatureKey.getBytes(StandardCharsets.UTF_8);
byte[] signatureKeyByte2 = genSignatureKey();
System.out.println(sign(content, signatureKeyByte));
System.out.println(sign(content, signatureKeyByte2));
}
}
如果有文件传输,需对文件加密:
- 每个要传输的文件file,生成唯一的密钥symmetric-key
- 用此symmetric-key根据AES-256-CBC算法对文件file进行加密,得到enc-file
- 将此symmetric-key用public-key加密得到enc-symmetric-key
- 将enc-symmetric-key和enc-file一同放进request中进行传输
- 接收方用private-key将enc-symmetric-key解密,得到symmetric-key
- 接收方用此symmetric-key解密文件enc-file,即可得到file
import static org.apache.commons.codec.digest.MessageDigestAlgorithms.SHA_256;
import static org.apache.commons.codec.digest.MessageDigestAlgorithms.SHA_512;
import static com.novacredit.mcra.mcracommon.common.util.crypto.PasswordHashUtil.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Properties;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.crypto.stream.CryptoInputStream;
import org.apache.commons.crypto.stream.CryptoOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class MyFileUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(MyFileUtils.
class);
public static final String HMAC_SHA_256 = HmacAlgorithms.HMAC_SHA_256.getName();
public static final String TRANSFORM = "AES/CBC/PKCS5Padding";
public static final String ALGORITHM_AES = "AES";
private MyFileUtils() {}
/**
* check sum
*
* @param file
* @return
* @throws IOException
*/
public static String getFileSHA512(File file)
throws IOException {
return new DigestUtils(SHA_512).digestAsHex(file);
}
/**
* check sum
*
* @param file
* @return
* @throws IOException
*/
public static String getFileSHA512Quietly(File file) {
try {
return new DigestUtils(SHA_512).digestAsHex(file);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* check sum for source string
*
* @param source
* @return
* @throws IOException
*/
public static String getStringSHA256(String source)
throws IOException {
return new DigestUtils(SHA_256).digestAsHex(source);
}
@Deprecated
public static byte[] makeKey(String prf,
byte[] pass,
byte[] salt,
int iter,
int len)
throws Exception {
byte[] result =
new byte[len];
Mac mac = Mac.getInstance(prf);
mac.init(
new SecretKeySpec(pass, prf));
byte[] saltcnt = Arrays.copyOf(salt, salt.length + 4);
while (
/* remaining */len > 0) {
for (
int o = saltcnt.length; --o >= saltcnt.length - 4;)
if (++saltcnt[o] != 0)
break;
byte[] u = saltcnt, x =
new byte[mac.getMacLength()];
for (
int i = 1; i <= iter; i++) {
u = mac.doFinal(u);
for (
int o = 0; o < x.length; o++)
x[o] ^= u[o];
}
int len2 = Math.min(len, x.length);
System.arraycopy(x, 0, result, result.length - len, len2);
len -= len2;
}
return result;
}
/**
* P.149
* P.79
* AES256-CBC
*
* @param inFilePath
* @param outFilePath
* @param symmetricKey
* @throws Exception
*/
public static void encryptFile(String inFilePath, String outFilePath, String symmetricKey)
throws Exception {
char[] pass = symmetricKey.toCharArray();
byte[] salt = (
new SecureRandom()).generateSeed(8);
// byte[] derive = makeKey(HMAC_SHA_256, pass, salt, PBKDF2_ITERATIONS, SALT_BYTE_SIZE);
byte[] derive = PasswordHashUtil.pbkdf2(pass, salt, PBKDF2_ITERATIONS, SALT_BYTE_SIZE);
Cipher cipher = Cipher.getInstance(TRANSFORM);
try (
FileInputStream fis =
new FileInputStream(inFilePath);
FileOutputStream fos =
new FileOutputStream(outFilePath);
CipherOutputStream cos =
new CipherOutputStream(fos, cipher);
){
// key is first 32, IV is last 16
cipher.init(
Cipher.ENCRYPT_MODE,
new SecretKeySpec(derive, 0, 32, ALGORITHM_AES),
new IvParameterSpec(derive, 32, 16)
);
fos.write("Salted__".getBytes(StandardCharsets.UTF_8));
fos.write(salt);
int b;
byte[] d =
new byte[8];
while ((b = fis.read(d)) != -1) {
cos.write(d, 0, b);
}
cos.flush();
// cos.close();
// fis.close();
}
}
@Deprecated
public static void decryptFileOld(String inFilePath, String outFilePath, String symmetricKey)
throws Exception {
try (FileOutputStream fos =
new FileOutputStream(outFilePath);){
byte[] file = Files.readAllBytes(Paths.get(inFilePath));
byte[] pass = symmetricKey.getBytes();
Cipher cipher = Cipher.getInstance(TRANSFORM);
// extract salt and derive
byte[] salt = Arrays.copyOfRange(file, 8, 16);
byte[] derive = makeKey(HMAC_SHA_256, pass, salt, PBKDF2_ITERATIONS, SALT_BYTE_SIZE);
// key is first 32, IV is last 16
cipher.init(
Cipher.DECRYPT_MODE,
new SecretKeySpec(derive, 0, 32, ALGORITHM_AES),
new IvParameterSpec(derive, 32, 16)
);
// remainder of file is ciphertext
// String contentDecoded = new String(cipher.doFinal(file,16,file.length-16) , StandardCharsets.UTF_8);
fos.write(cipher.doFinal(file,16,file.length-16));
// fos.close();
}
}
public static void decryptFile(String inFilePath, String outFilePath, String symmetricKey)
throws Exception {
try(
FileInputStream fis =
new FileInputStream(inFilePath);
FileOutputStream fos =
new FileOutputStream(outFilePath,
true);
){
char[] pass = symmetricKey.toCharArray();
Cipher cipher = Cipher.getInstance(TRANSFORM);
int i=0;
int b;
byte[] bytes =
new byte[2048];
while ((b = fis.read(bytes)) != -1) {
byte[] contentBytes = Arrays.copyOf(bytes, b);
byte[] content =
null;
if(i == 0) {
byte[] salt = Arrays.copyOfRange(bytes, 8, 16);
// byte[] derive = makeKey(HMAC_SHA_256, pass, salt, PBKDF2_ITERATIONS, SALT_BYTE_SIZE);
byte[] derive = PasswordHashUtil.pbkdf2(pass, salt, PBKDF2_ITERATIONS, SALT_BYTE_SIZE);
cipher.init(
Cipher.DECRYPT_MODE,
new SecretKeySpec(derive, 0, 32, ALGORITHM_AES),
new IvParameterSpec(derive, 32, 16)
);
content = cipher.doFinal(contentBytes, 16, contentBytes.length-16);
}
else {
content = cipher.doFinal(contentBytes);
}
fos.write(content);
fos.flush();
i++;
}
}
catch(Exception e) {
LOGGER.error(e.getMessage(),e);
throw e;
}
}
private static byte[] getUTF8Bytes(
final String input) {
return input.getBytes(StandardCharsets.UTF_8);
}
public static void encryptFile2(String inFilePath, String outFilePath, String symmetricKey)
throws Exception {
byte [] semmetryKeyByte = getUTF8Bytes(symmetricKey);
final SecretKeySpec key =
new SecretKeySpec(semmetryKeyByte, ALGORITHM_AES);
final IvParameterSpec iv =
new IvParameterSpec(semmetryKeyByte);
final Properties properties =
new Properties();
//Encryption with CryptoOutputStream.
try (
FileInputStream fis =
new FileInputStream(inFilePath);
FileOutputStream fos =
new FileOutputStream(outFilePath);
CryptoOutputStream cos =
new CryptoOutputStream(TRANSFORM, properties, fos, key, iv)
) {
byte[] buf =
new byte[1024 * 16];
int len;
while ((len = fis.read(buf)) != -1) {
cos.write(buf, 0, len);
}
// fis.close();
cos.flush();
}
}
public static void decryptFile2(String inFilePath, String outFilePath, String semmetryKey)
throws Exception {
byte [] semmetryKeyByte = getUTF8Bytes(semmetryKey);
final SecretKeySpec secretKey =
new SecretKeySpec(semmetryKeyByte, ALGORITHM_AES);
final IvParameterSpec initializationVector =
new IvParameterSpec(semmetryKeyByte);
Properties properties =
new Properties();
try (
FileInputStream fis =
new FileInputStream(inFilePath);
FileOutputStream fos =
new FileOutputStream(outFilePath);
CryptoInputStream cis =
new CryptoInputStream(TRANSFORM, properties, fis, secretKey,
initializationVector);
){
byte[] buf =
new byte[1024 * 16];
int len;
while ((len = cis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
// cis.close();
fos.flush();
}
}
}
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.StringUtils;
public class SymmetricKeyUtil {
private SymmetricKeyUtil() {}
/**
* P.79
*
* @param symmetricKey
* @param publicKey
* @return
* @throws Exception
*/
public static String encryptWithPublicKey(String symmetricKey, PublicKey publicKey) throws Exception {
// --- encrypt given algorithm string
Cipher oaepFromAlgo = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");
oaepFromAlgo.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] result = oaepFromAlgo.doFinal(StringUtils.getBytesUtf8(symmetricKey));
return Base64.getEncoder().encodeToString(result);
}
public static String decryptWithPrivateKey(String symmetricKey, PrivateKey privateKey) throws Exception {
// --- encrypt given algorithm string
Cipher oaepFromAlgo = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");
// OAEPParameterSpec oaepParams =
// new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), PSource.PSpecified.DEFAULT);
// oaepFromAlgo.init(Cipher.DECRYPT_MODE, privateKey, oaepParams);
oaepFromAlgo.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decodedContent = Base64.getDecoder().decode(symmetricKey);
byte[] result = oaepFromAlgo.doFinal(decodedContent);
return StringUtils.newStringUtf8(result);
}
}
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
public class PasswordHashUtil {
public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256";
public static final int PBKDF2_ITERATIONS = 10000;
public static final int HASH_BYTE_SIZE = 48;
public static final int SALT_BYTE_SIZE = 48;
private PasswordHashUtil() {}
/**
* Computes the PBKDF2 hash of a password.
*
* @param password
* the password to hash.
* @param salt
* the salt
* @param iterations
* the iteration count (slowness factor)
* @param bytes
* the length of the hash to compute in bytes
* @return the PBDKF2 hash of the password
*/
protected static byte[] pbkdf2(final char[] password, final byte[] salt, final int iterations, final int bytes)
throws NoSuchAlgorithmException, InvalidKeySpecException {
final PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, bytes * 8);
final SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
return skf.generateSecret(spec).getEncoded();
}
/**
* Returns a salted PBKDF2 hash of the password.
*
* @param password
* the password to hash
* @return a salted PBKDF2 hash of the password
*/
public static byte[] createHash(final char[] password) throws NoSuchAlgorithmException, InvalidKeySpecException {
// Generate a random salt
final SecureRandom random = new SecureRandom();
final byte[] salt = new byte[SALT_BYTE_SIZE];
random.nextBytes(salt);
// Hash the password
final byte[] hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE);
// format iterations:salt:hash
// return PBKDF2_ITERATIONS + ":" + toEncoding(salt) + ":" + toEncoding(hash);
return hash;
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public final class PasswordGenerator {
private static final String LOWER = "abcdefghijklmnopqrstuvwxyz";
private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String DIGITS = "0123456789";
private static final String PUNCTUATION = "!@#$%&*()_+-=[]|,./?><";
private boolean useLower;
private boolean useUpper;
private boolean useDigits;
private boolean usePunctuation;
private PasswordGenerator() {
throw new UnsupportedOperationException("Empty constructor is not supported.");
}
private PasswordGenerator(PasswordGeneratorBuilder builder) {
this.useLower = builder.useLower;
this.useUpper = builder.useUpper;
this.useDigits = builder.useDigits;
this.usePunctuation = builder.usePunctuation;
}
public static class PasswordGeneratorBuilder {
private boolean useLower;
private boolean useUpper;
private boolean useDigits;
private boolean usePunctuation;
public PasswordGeneratorBuilder() {
this.useLower =
false;
this.useUpper =
false;
this.useDigits =
false;
this.usePunctuation =
false;
}
/**
* Set true in case you would like to include lower characters
* (abcxyz). Default false.
*
* @param useLower true in case you would like to include lower
* characters (abcxyz). Default false.
* @return the builder for chaining.
*/
public PasswordGeneratorBuilder useLower(
boolean useLower) {
this.useLower = useLower;
return this;
}
/**
* Set true in case you would like to include upper characters
* (ABCXYZ). Default false.
*
* @param useUpper true in case you would like to include upper
* characters (ABCXYZ). Default false.
* @return the builder for chaining.
*/
public PasswordGeneratorBuilder useUpper(
boolean useUpper) {
this.useUpper = useUpper;
return this;
}
/**
* Set true in case you would like to include digit characters (123..).
* Default false.
*
* @param useDigits true in case you would like to include digit
* characters (123..). Default false.
* @return the builder for chaining.
*/
public PasswordGeneratorBuilder useDigits(
boolean useDigits) {
this.useDigits = useDigits;
return this;
}
/**
* Set true in case you would like to include punctuation characters
* (!@#..). Default false.
*
* @param usePunctuation true in case you would like to include
* punctuation characters (!@#..). Default false.
* @return the builder for chaining.
*/
public PasswordGeneratorBuilder usePunctuation(
boolean usePunctuation) {
this.usePunctuation = usePunctuation;
return this;
}
/**
* Get an object to use.
*
* @return the {@link gr.idrymavmela.business.lib.PasswordGenerator}
* object.
*/
public PasswordGenerator build() {
return new PasswordGenerator(
this);
}
}
/**
* This method will generate a password depending the use* properties you
* define. It will use the categories with a probability. It is not sure
* that all of the defined categories will be used.
*
* @param length the length of the password you would like to generate.
* @return a password that uses the categories you define when constructing
* the object with a probability.
*/
public String generate(
int length) {
// Argument Validation.
if (length <= 0) {
return "";
}
// Variables.
StringBuilder password =
new StringBuilder(length);
Random random =
new Random(System.nanoTime());
// Collect the categories to use.
List<String> charCategories =
new ArrayList<>(4);
if (useLower) {
charCategories.add(LOWER);
}
if (useUpper) {
charCategories.add(UPPER);
}
if (useDigits) {
charCategories.add(DIGITS);
}
if (usePunctuation) {
charCategories.add(PUNCTUATION);
}
// Build the password.
for (
int i = 0; i < length; i++) {
String charCategory = charCategories.get(random.nextInt(charCategories.size()));
int position = random.nextInt(charCategory.length());
password.append(charCategory.charAt(position));
}
return new String(password);
}
public static String genDefaultPassword() {
return new PasswordGeneratorBuilder()
.useDigits(
true)
.usePunctuation(
true)
.useUpper(
true)
.useLower(
true)
.build()
.generate(16);
}
public static String genDefault32Password() {
return new PasswordGeneratorBuilder()
.useDigits(
true)
.usePunctuation(
true)
.useUpper(
true)
.useLower(
true)
.build()
.generate(32);
}
public static void main(String [] args) {
String password =
new PasswordGeneratorBuilder()
.useDigits(
true)
.usePunctuation(
true)
.useUpper(
true)
.useLower(
true)
.build()
.generate(8);
System.out.println(password);
}
}
单元测试
import java.io.File;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.novacredit.mcra.democrpcp.test.base.AbstractTestApp;
import com.novacredit.mcra.mcracommon.common.util.crypto.MyFileUtils;
import com.novacredit.mcra.mcracommon.common.util.crypto.PasswordGenerator;
public class EncryptFileTest {
private String inputFilePath = AbstractTestApp.getAbsolutePath(
"src/test/resources/KCLO_WS01_20210531_103933_ENQ.zip");
private String encryptFilePath = inputFilePath.replace(".zip", ".enc.zip");
private String password = PasswordGenerator.genDefaultPassword();
private String decryptFilePath = inputFilePath.replace(".zip", ".dec.zip");
@BeforeEach
public void deleteFile() {
FileUtils.deleteQuietly(new File(encryptFilePath));
FileUtils.deleteQuietly(new File(decryptFilePath));
}
@Test
public void testEncryptFile() throws Exception {
MyFileUtils.encryptFile(inputFilePath, encryptFilePath, password);
MyFileUtils.decryptFile(encryptFilePath, decryptFilePath, password);
}
@Test
public void testEncryptFile2() throws Exception {
MyFileUtils.encryptFile2(inputFilePath, encryptFilePath, password);
MyFileUtils.decryptFile2(encryptFilePath, decryptFilePath, password);
// FileUtils.deleteQuietly(new File());
}
}
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Base64;
import org.junit.jupiter.api.Test;
import com.nimbusds.jose.JOSEException;
import com.novacredit.mcra.mcracommon.common.util.crypto.PasswordGenerator;
import com.novacredit.mcra.mcracommon.common.util.crypto.SignatureUtil;
public class SignatureUtilTest {
private String content = "Hello, world!";
@Test
public void test1() throws JOSEException, ParseException{
// String signatureKey = "a0a2abd8-6162-41c3-83d6-1cf559b46afc";
String signatureKey = PasswordGenerator.genDefault32Password();
// String signatureKey = SignatureUtil.genSignatureKeyString();
System.out.println(signatureKey);
byte[] signatureKeyByte = signatureKey.getBytes(StandardCharsets.UTF_8);
String result = SignatureUtil.sign(content, signatureKeyByte);
System.out.println(result);
assertTrue(SignatureUtil.verify(result, signatureKeyByte));
assertEquals(content, SignatureUtil.verifyAndGet(result, signatureKeyByte));
}
@Test
public void test2() throws JOSEException, ParseException{
byte[] signatureKeyByte = SignatureUtil.genSignatureKey();
String signatureKeyString = Base64.getEncoder().encodeToString(signatureKeyByte);
byte [] decodeSignatureKeyByte = SignatureUtil.decodeSignatureKey(signatureKeyString);
System.out.println("signatureKeyByte == decodeSignatureKeyByte ? " + Arrays.equals(signatureKeyByte, decodeSignatureKeyByte));
String result = SignatureUtil.sign(content, signatureKeyByte);
System.out.println(result);
assertTrue(SignatureUtil.verify(result, signatureKeyByte));
assertEquals(content, SignatureUtil.verifyAndGet(result, signatureKeyByte));
}
}
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import org.junit.jupiter.api.Test;
import com.novacredit.mcra.mcracommon.common.util.crypto.PublicCertificateUtil;
import com.novacredit.mcra.mcracommon.common.util.crypto.SymmetricKeyUtil;
public class SymmetricKeyUtilTest {
@Test
public void test1() throws Exception {
KeyPair keyPair = PublicCertificateUtil.genKeyPair();
String privateKeyString = PublicCertificateUtil.keyToBase64String(keyPair.getPrivate());
String publicKeyString = PublicCertificateUtil.keyToBase64String(keyPair.getPublic());
System.out.println(privateKeyString);
System.out.println(publicKeyString);
String content = "Hello World.";
PrivateKey privateKey = PublicCertificateUtil.genPrivateKey(privateKeyString);
PublicKey publicKey = PublicCertificateUtil.genPublicKey(publicKeyString);
String encryptResult = SymmetricKeyUtil.encryptWithPublicKey(content, publicKey);
String decryptResult = SymmetricKeyUtil.decryptWithPrivateKey(encryptResult, privateKey);
assertEquals(decryptResult, content);
}
}