일 대칭 암호화
대칭키 암호화와 비대칭키 암호화는 서로 다른 두 가지 암호화 방식으로, 데이터 보안과 정보 전송에서 중요한 역할을 한다。아래에서 이 두 암호화 기술을 각각 소개하겠다:
1.1 대칭 암호화(Symmetric Encryption)
대칭 암호화는 암호화와 복호화에 동일한 키를 사용하는 방식입니다. 따라서 발신자와 수신자 모두 이 키를 가지고 있어야 암호화와 복호화를 수행할 수 있습니다.
일반적인 대칭 암호화 알고리즘:
AES(고급 암호화 표준)
DES(데이터 암호화 표준)
3DES(삼중 데이터 암호화 알고리즘)
1.2 대칭 암호화의 특징
일반적으로 대칭 암호화는 다음과 같은 특징이 있다:
속도가 비교적 빠름:암호화와 복호화에 동일한 키를 사용하므로, 대칭 암호화는 일반적으로 비대칭 암호화보다 빠르다.
키 관리:대칭 암호화의 주요 과제는 키의 배포와 관리에 있다. 키가 유출되면 암호화의 안전성이 위협받는다.
대량의 데이터에 적합:속도가 빠르므로, 대칭 키 암호화는 대량의 데이터를 암호화하는 데 적합합니다.
여기서 대칭 암호화에는 크게 두 가지 장점이 있음을 알 수 있다. 첫째는 연산 속도가 빠르다는 것이고, 둘째는 대량의 데이터에 적합하다는 것이다.
하지만 대칭 암호화에는 치명적인 문제가 하나 있는데, 바로 키 관리입니다. 서버에서 클라이언트로 키를 안전하게 전송하려면 어떻게 해야 할까요! 또한 일대다 통신에서는 키가 유출되지 않도록 잘 관리하는 것도 어려운 과제입니다. 이것이 대칭 암호화의 한계입니다.

1.3 코드 예제
이어서 쑹 형이 Java 코드로 대칭 암호화와 복호화를 구현하는 방법을 보여 드리겠습니다.
Java에서 대칭 암호화를 구현할 때는 일반적으로 Java 암호화 아키텍처(Java Cryptography Architecture, JCA)가 제공하는 클래스와 인터페이스를 사용합니다.
다음은 AES(고급 암호화 표준)알고리즘으로 대칭 암호화와 복호화를 수행하는 간단한 예제다:
public class SymmetricEncryptionExample { // 生成密钥
public static SecretKey generateKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128); // 可以是128, 192或256位
return keyGenerator.generateKey();
}
// 加密方法
public static String encrypt(String data, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
}
// 解密方法
public static String decrypt(String encryptedData, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
return new String(decryptedBytes);
}
public static void main(String[] args) {
try {
// 生成密钥
SecretKey key = generateKey();
// 原始数据
String originalData = "Hello, JavaBoy!";
// 加密
String encryptedData = encrypt(originalData, key);
System.out.println("加密数据: " + encryptedData);
// 解密
String decryptedData = decrypt(encryptedData, key);
System.out.println("解密数据: " + decryptedData);
} catch (Exception e) {
e.printStackTrace();
}
}
}키 생성:사용
KeyGeneratorAES 키를 생성합니다.암호화:사용
Cipher클래스로 암호화하며, 데이터를 바이트로 변환한 뒤 암호화하고, 저장하거나 전송하기 쉽도록 Base64 인코딩을 사용해 문자열로 변환한다.복호화:암호화된 문자열을 다시 바이트로 디코딩한 뒤, 동일한 키로 복호화합니다.
위 코드에서 주의해야 할 점은 다음과 같다:
키 길이(예: 128비트)는 보안 요구 사항에 따라 선택해야 한다.
키를 안전하게 보관하고, 코드에 키를 하드코딩하지 않도록 한다.
운영 환경에서는 더 안전한 키 관리 전략의 사용을 고려해야 합니다.
보안을 고려하여 일반적으로 위의 방식으로 키를 생성한다. 이 방식으로 생성한 키는 시스템을 재시작할 때마다 바뀐다는 특징이 있다. 키 생성을 직접 제어하고 싶다면 다음과 같은 방식으로 키를 생성할 수 있다:
public static SecretKey generateKeyFromPassword(String password, int keySize) throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), "salt".getBytes(), 65536, keySize);
return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
}이렇게 하면 직접 전달한 매개변수로 키를 제어할 수 있다.
1.4 프런트엔드와 백엔드 연동
서로 다른 언어(예: JavaScript와 Java)에서 AES 알고리즘으로 암호화와 복호화를 수행할 때는 양쪽에서 동일한 키, 알고리즘 모드(예: CBC, ECB 등), 패딩 모드(예: PKCS5Padding, PKCS7Padding 등), 초기화 벡터(IV, CBC처럼 IV가 필요한 모드를 사용하는 경우)를 사용하도록 하는 것이 핵심입니다.
앞서 어떤 분이 프런트엔드에서 암호화한 뒤 백엔드에서 계속 복호화할 수 없다고 했다. 여기서 쑹거도 프런트엔드와 백엔드를 연동하는 예제를 하나 제시하겠다.
프런트엔드 암호화와 백엔드 복호화
프런트엔드 코드:
<!DOCTYPE html><html lang="en"><head>
<meta charset="UTF-8">
<title>AES加密示例</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
</head>
<body>
<script>
function encryptAES(text, secretKey) {
const key = CryptoJS.enc.Utf8.parse(secretKey);
const iv = CryptoJS.lib.WordArray.random(128 / 8);
// 对于CBC模式,需要IV
const encrypted = CryptoJS.AES.encrypt(text, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
// 返回加密后的文本和IV(Base64格式),实际使用中可能需要安全地传输这些值
return {
ciphertext: encrypted.toString(),
iv: iv.toString(CryptoJS.enc.Base64)
};
}
const secretKey = 'helloworldhelloworldhelloworld11'; /
/ 确保密钥是32个字符长(256位)
const text = 'Hello, javaboy!';
const result = encryptAES(text, secretKey);
console.log(result);
</script>
</body>
</html>백엔드 코드:
import javax.crypto.Cipher;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import java.util.Base64;public class AESDecrypt { public static String decryptAES(String encryptedData, String secretKey, String iv) throws Exception { IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.getDecoder().decode(iv)); SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes("UTF-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); byte[] decodedValue = Base64.getDecoder().decode(encryptedData); byte[] decryptedValue = cipher.doFinal(decodedValue); return new String(decryptedValue, "UTF-8");
} public static void main(String[] args) { try { //前端加密后的文本
String encryptedText = "PYANpAjMsRnBIEhovtEXQw=="; String secretKey = "helloworldhelloworldhelloworld11"; //前端 IV,要和加密后的文本一起传到后端
String iv = "y/jUHcgSOpOiyNlsfjNUBg=="; String decryptedText = decryptAES(encryptedText, secretKey, iv);
System.out.println("解密文本: " + decryptedText);
} catch (Exception e) {
e.printStackTrace();
}
}
}여기서 프런트엔드가 암호화할 때 iv 매개변수가 생성되므로, 프런트엔드 암호화 결과와 함께 백엔드로 전달해야 한다는 점에 유의해야 한다。
백엔드 암호화 프런트엔드 복호화
Java에서 AES로 암호화하고 JavaScript에서 복호화할 때에도 양쪽이 동일한 키、알고리즘 모드(예: CBC、ECB 등)、패딩 모드(예: PKCS5Padding、PKCS7Padding 등) 및(해당하는 경우)동일한 초기화 벡터(IV)를 사용하도록 해야 합니다.
백엔드 코드:
public class AESEncrypt { public static String encryptAES(String plainText, String secretKey, String iv) throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256); // 对于AES-256
SecretKey secret = new SecretKeySpec(secretKey.getBytes("UTF-8"), "AES"); IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.getDecoder().decode(iv)); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret, ivParameterSpec); byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8")); return Base64.getEncoder().encodeToString(encrypted);
} public static void main(String[] args) { try { String plainText = "Hello, 江南一点雨!"; String secretKey = "helloworldhelloworldhelloworld11"; // 确保密钥是32个字符长(256位)
String iv = Base64.getEncoder().encodeToString(new byte[16]); // 示例IV,实际应用中应更安全地生成
String encryptedText = encryptAES(plainText, secretKey, iv);
System.out.println("Encrypted text: " + encryptedText);
System.out.println("IV (Base64): " + iv); // 确保将IV发送给解密方
} catch (Exception e) {
e.printStackTrace();
}
}
}프런트엔드 코드:
<!DOCTYPE html><html lang="en"><head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
<script>
function decryptAES(ciphertext, secretKey, iv) { const key = CryptoJS.enc.Utf8.parse(secretKey); const ivParsed = CryptoJS.enc.Base64.parse(iv); const decrypted = CryptoJS.AES.decrypt(
{ ciphertext: CryptoJS.enc.Base64.parse(ciphertext)
},
key,
{ iv: ivParsed, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7
}
); return decrypted.toString(CryptoJS.enc.Utf8);
} const secretKey = 'helloworldhelloworldhelloworld11'; const iv = 'AAAAAAAAAAAAAAAAAAAAAA=='; // 从Java代码获取
const ciphertext = '1lPNVF1injas78KUWeKp5FusEr6f0pGgcrLAg9ELFr8='; // 从Java代码获取
const decryptedText = decryptAES(ciphertext, secretKey, iv); console.log(decryptedText); </script></head><body></body></html>위의 프런트엔드와 백엔드 간 통신 암복호화 코드는 쑹 형이 직접 테스트했으며 모두 문제가 없었다. 이와 관련한 필요가 있다면 이 글을 바로 저장해 두고 참고하기 바란다.
둘째 비대칭 암호화
2.1 비대칭 암호화란 무엇인가
공개 키 암호화라고도 하는 비대칭 암호화는 서로 다른 두 키(공개 키와 개인 키)를 사용하는 암호화 방식이다.
일반적으로 비대칭 암호화에는 다음과 같은 몇 가지 특징이 있습니다:
공개 키와 개인 키
공개 키와 개인 키의 생성: 비대칭 암호화는 한 쌍의 키를 사용한다. 공개 키는 공개되어 누구나 접근할 수 있지만, 개인 키는 비공개로 키 보유자만 접근할 수 있다. 두 키는 수학적 알고리즘으로 생성되며 서로 연관되어 있지만, 어느 한쪽에서 다른 한쪽을 도출할 수는 없다.
높은 안전성
해독하기 어려움:공개 키와 개인 키 사이의 복잡한 수학적 관계 덕분에 비대칭 암호화는 보안성이 높다. 공격자가 공개 키로부터 개인 키를 추론하기는 매우 어려우므로, 암호화된 데이터의 안전성이 보장된다.
양자 컴퓨팅 공격 대응:일부 블록체인 프로젝트는 미래 양자 컴퓨팅의 위협에 대비하여,타원 곡선 디지털 서명 알고리즘(ECDSA)의 양자 내성 변형과 같은 양자 컴퓨팅 공격에 강한 암호화 알고리즘을 도입하기 시작했다。
공개 키를 배포하기 편리함
공개 키의 공개성:공개 키는 누구에게나 공개할 수 있으므로 배포가 매우 편리합니다. 누구나 공개 키를 사용해 데이터를 암호화할 수 있지만, 개인 키 보유자만 복호화할 수 있습니다.
신원 인증과 디지털 서명
신원 인증:공개 키를 사용자의 신원 식별자로 사용할 수 있으며,다른 사람은 사용자의 개인 키를 알지 못해도 신원을 검증할 수 있다。이는 블록체인과 같은 탈중앙화 네트워크에서 신뢰할 수 있는 신원을 확립하는 데 도움이 된다。
전자 서명:개인 키 보유자는 개인 키로 메시지에 서명할 수 있고, 다른 사람은 공개 키로 서명의 진위를 검증할 수 있습니다. 이를 통해 메시지의 무결성과 출처의 신뢰성을 보장합니다.
안전한 통신
암호화 통신:공개 키로 메시지를 암호화할 수 있으며, 이에 대응하는 개인 키를 가진 사람만 복호화할 수 있다. 이는 통신 과정에서 데이터 보안을 보장하고, 권한이 없는 사람이 정보에 접근하는 것을 막는다.
디지털 자산 제어
블록체인 주소와 개인 키:블록체인 주소는 보통 공개 키에서 파생되며, 사용자는 개인 키로 해당 주소에 연결된 디지털 자산을 제어한다. 개인 키의 보안은 디지털 자산의 안전에 매우 중요하다.
암호화 및 복호화 속도
상대적으로 느림:비대칭 암호화는 더 복잡한 수학 연산이 필요하므로 암호화와 복호화 속도가 대칭 암호화보다 훨씬 느리다. 그러나 이는 보안 통신, 인증 등의 분야에서 활용하는 데 지장을 주지는 않는다.
2.2 비대칭 암호화의 용도
비대칭 암호화에는 두 가지 대표적인 활용 사례가 있다.
암호화:가장 익숙한 사용법으로, 공개 키로 암호화하고 개인 키로 복호화하는 것입니다.
서명:네트워크를 신뢰할 수 없고 전송 중 데이터가 변조될 수 있다는 점을 고려하면,이때는 공개 키와 개인 키를 반대로 사용할 수 있습니다. 개인 키로 데이터에 서명하고,공개 키로 서명을 검증하여 데이터의 안전성과 무결성을 보장합니다.
두 번째 용도에 관해 어떤 분들은 개인 키로 암호화하고 공개 키로 복호화한다고 표현하기도 합니다. 어쨌든 같은 이야기를 하는 것임은 다들 알고 있습니다.
비대칭 암호화 알고리즘은 이론적으로 데이터 암호화와 디지털 서명에 사용할 수 있지만, 실제로는 높은 연산 복잡도와 낮은 효율이 주된 장애물이 됩니다.
대칭 암호화 알고리즘에 비해,비대칭 암호화의 연산 속도는 몇 자릿수나 느리며,이는 대용량 데이터를 처리하는 능력에 큰 영향을 미친다。또한,비대칭 암호화 알고리즘의 암호화 및 복호화 과정은 키 길이와 밀접하게 관련되어 있고,블록 암호 운용 모드를 지원하지 않기 때문에,키 길이를 넘지 않는 소량의 데이터만 처리할 수 있어 대량의 데이터를 암호화할 수 없다。
비대칭 암호화의 성능상 한계를 극복하기 위해 현대 암호화 시스템은 일반적으로 대칭 암호화와 비대칭 암호화의 장점을 결합한 하이브리드 암호화 전략을 사용합니다. 이 전략에서 비대칭 암호화는 주로 대칭 암호화 키 하나를 상대방에게 안전하게 전달하는 데(즉, “키 합의”에)사용됩니다. 양측이 이 대칭 키를 안전하게 공유하고 나면, 효율적인 대칭 암호화 알고리즘으로 대량의 데이터를 암호화하고 복호화할 수 있습니다. 이러한 결합 방식은 암호화 효율을 높이고 통신 보안도 강화하므로, SSL/TLS 등 다양한 보안 통신 프로토콜에서 널리 사용됩니다.
디지털 서명 분야에서도 비대칭 암호화의 효율을 높이기 위해 몇 가지 조정을 했다. 구체적으로는 먼저 원본 데이터에 해시 처리를 하여(MD5、SHA 등을 사용할 수 있다), 고정 길이의 해시값을 얻는다. 그런 다음 비대칭 암호화 알고리즘으로 이 해시값을 암호화하여 디지털 서명을 생성한다. 해시 알고리즘은 임의 길이의 데이터를 고정 길이의 해시값으로 효율적으로 압축할 수 있으므로, 원본 데이터가 아무리 커도 해시 데이터의 길이는 같으며 서명 과정도 효율적으로 유지할 수 있다. 서명을 검증할 때는 원본 데이터의 해시값을 다시 계산하고, 복호화한 서명과 비교하기만 하면 데이터의 변조 여부를 빠르게 판단할 수 있다.
2.3 암호화 예제
Java 코드로 RSA 암호화와 복호화를 구현한 예제:
public class RsaDemo { public static void main(String[] args) { try { KeyPair keyPair = generateRSAKeyPair(); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); String originalData = "Hello, 江南一点雨!"; String encryptedData = encrypt(publicKey, originalData); String decryptedData = decrypt(privateKey, encryptedData);
System.out.println("加密后的数据: " + encryptedData);
System.out.println("解密后的数据: " + decryptedData);
} catch (Exception e) {
e.printStackTrace();
}
} public static String decrypt(PrivateKey privateKey, String encryptedData) throws Exception { Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData)); return new String(decryptedBytes);
} public static String encrypt(PublicKey publicKey, String data) throws Exception { Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] bytes = cipher.doFinal(data.getBytes()); return Base64.getEncoder().encodeToString(bytes);
} public static KeyPair generateRSAKeyPair() throws NoSuchAlgorithmException { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048); // 可以指定密钥长度,如2048位
return keyGen.generateKeyPair();
}
}2.4 프런트엔드와 백엔드 연동
프런트엔드 암호화와 백엔드 복호화
백엔드 코드는 위 사례와 같으며, 다른 점은 공개 키를 얻은 뒤 출력할 수 있다는 것이다. 이 공개 키는 나중에 프런트엔드로 전달해야 한다:
public static void main(String[] args) { try { KeyPair keyPair = generateRSAKeyPair(); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate();
System.out.println("公钥: " + Base64.getEncoder().encodeToString(publicKey.getEncoded())); String originalData = "Hello, 江南一点雨!"; String encryptedData = encrypt(publicKey, originalData); String decryptedData = decrypt(privateKey, encryptedData);
System.out.println("加密后的数据: " + encryptedData);
System.out.println("解密后的数据: " + decryptedData);
} catch (Exception e) {
e.printStackTrace();
}
}프런트엔드 코드는 다음과 같다:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>RSA Encryption Example</title> <!-- 引入jsencrypt库 --> <script src="https://cdn.jsdelivr.net/npm/[email protected]/bin/jsencrypt.min.js"></script></head><body><script> // 重写前端加密方法 function encryptData(publicKey, data) { // 创建一个新的JSEncrypt对象 var encryptor = new JSEncrypt(); // 设置公钥 encryptor.setPublicKey(publicKey); // 加密数据 var encrypted = encryptor.encrypt(data); return encrypted; } // 示例公钥(实际使用时应该替换为服务器提供的公钥) var publicKey = `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnP5FOXIghidLDE2FgFwzi4Y+qTdYjnc0eMoiL5i4gdOeTE+7Uu9YvDB51GcBWKD9lvmZr5rX8z0OBpwe526qsTekNpXoIVk9DB34US0HOrAXEEwpUTVuSR656cJGAwmWBkVaalQynEz4Dlzrm53zExVYueruYUuzFyuZDaQcFnl3rWUH/XDkCIe23z0R1TfT3Q2OYNKft0u56r0S/ko99utXuYJK9yowe7QGT6q4cSJwsITQTomCARAq9q+bSNuGEYa4FlYCKIKWIhKbMhz0FYIMB2fJN10GyZbbvKASqeMkuCoD2Efgd8/6uMwOaMcx9LgEkcFaQ3qgDutsPXNUswIDAQAB`; // 要加密的数据 var data = 'Hello, javaboy!'; // 调用加密方法 var encryptedData = encryptData(publicKey, data); // 输出加密后的数据 console.log('Encrypted Data:', encryptedData);</script></body></html>
백엔드 암호화 프런트엔드 복호화
Java에서 RSA 알고리즘으로 데이터를 암호화하고 JavaScript에서 해당 데이터를 복호화한다는 것은 서버가 프런트엔드의 공개 키로 암호화하고, 프런트엔드는 자신의 개인 키로 복호화한다는 뜻이다. 이런 상황에서는 프런트엔드의 개인 키가 쉽게 탈취될 수 있으므로 이러한 사용 방식은 권장하지 않는다. 따라서 예제도 들지 않겠다.
자, 이렇게 여러분과 대칭 암호화와 비대칭 암호화에 대해 다시 한번 이야기해 보았다. 위의 예제 코드는 모두 나 쑹거가 테스트를 통과시킨 것이니 참고해도 좋다.