협력사에서 AES256 방식으로 암호화 된 데이터를 제공해주기로 했다.
내가 받은것은 암호화 키 달랑 하나뿐...
거기에 협력사에서 키를 잘못 처리해서 사용해서 전달받은 값은 복호화가 되지 않는 상황
그래서 적어보는 AES256 스펙 및 구현에 대한 정보이다.
AES는 Advanced Encryption Standard 고오급 암호화 표준이다.
암호화 및 복호화에 동일한 키를 사용하는 대칭키 알고리즘이다.
종류로는 AES-128, AES-192, AES-256 이 있는데
각 뒤에 붙은 숫자가 키의 길이를 의미한다.
ex) AES-256 => 키가 256bit(=32byte)
AES 의 암/복호화는 다음과 같은 과정으로 이루어진다.
plain text
> plain bytes
> encrypt
> encrypted bytes
> encrypted base64 text
encrpyted base64 text
> encrypted bytes
> decrypt
> plain bytes
> plain text
그렇다면 AES 암호화 복호화에는 무엇이 필요할까? 크게 네가지가 있다.
(각 항목이 의미하는 바는 각자 알아서 찾아보시기를 바란다.)
여기서 뭐 하나라도 다르면 결과가 다르게 나온다.
위에 네가지가 반드시 전달되어야 하는것이고
Java 구현
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Main {
public static void main(String[] args) throws Exception {
String plainText = "저를 암호화 해주세요";
String encrypted = encrypt(plainText);
System.out.printf("PLAIN_TEXT:: %s\n", plainText);
System.out.printf("ENCRYPTED:: %s\n", encrypted);
System.out.printf("DECRYPTED:: %s\n", decrypt(encrypted));
}
public static String Alg = "AES/CBC/PKCS5Padding";
public static String PK = "01234567890123456789012345678901"; // 32byte
public static String IV = PK.substring(0,16); // 16byte
public static String encrypt(String plainText) throws Exception {
Cipher cipher = Cipher.getInstance(Alg);
SecretKeySpec keySpec = new SecretKeySpec(IV.getBytes(), "AES");
IvParameterSpec ivParamSpec = new IvParameterSpec(IV.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParamSpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(encrypted);
}
public static String decrypt(String cipherText) throws Exception {
Cipher cipher = Cipher.getInstance(Alg);
SecretKeySpec keySpec = new SecretKeySpec(IV.getBytes(), "AES");
IvParameterSpec ivParamSpec = new IvParameterSpec(IV.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParamSpec);
byte[] decodedBytes = Base64.getDecoder().decode(cipherText);
byte[] decrypted = cipher.doFinal(decodedBytes);
return new String(decrypted, "UTF-8");
}
}
Node.js 구현(typescript)
const privateKey = "01234567890123456789012345678901"; // 32byte
const ivKey = privateKey.substring(0, 16); // 16byte
const chainingMode = "AES-256-CBC";
const encrypt = (utf8Text: string) => {
const cipher = crypto.createCipheriv(chainingMode, privateKey, ivKey);
cipher.setAutoPadding(false);
let encrypted = cipher.update(pkcs7Pad(utf8Text), undefined, "base64");
encrypted += cipher.final("base64");
return encrypted;
};
const decrypt = (base64Text: string) => {
const decipher = crypto.createDecipheriv(chainingMode, privateKey, this._ivKey);
decipher.setAutoPadding(false);
let decrypted = decipher.update(base64Text, "base64", "utf8");
decrypted += decipher.final("utf8");
return pkcs7Unpad(decrypted);
};
const pkcs7 = require("pkcs7");
const pkcs7Pad = (params: string) => {
const buffer = Buffer.from(params, "utf8");
const bytes = new Uint8Array(buffer.length);
let i = buffer.length;
while (i--) {
bytes[i] = buffer[i];
}
return Buffer.from(pkcs7.pad(bytes) as Uint8Array);
}
const pkcs7Unpad = (params: string) => {
const buffer = Buffer.from(params, "utf8");
const bytes = new Uint8Array(buffer.length);
let i = buffer.length;
while (i--) {
bytes[i] = buffer[i];
}
const result = Buffer.from(pkcs7.unpad(bytes) as Uint8Array);
return result.toString("utf8");
}
const plainText = "저를 암호화 해주세요";
const encrypted = encrypt(plainText);
console.log("PLAIN_TEXT", plainText);
console.log("ENCRYPTED", encrypted);
console.log("DECRYPTED", decrypt(encrypted));
p.s. 인터넷에서 코드 긁어와서 쓰더라도, 쓸때는 개념이라도 찾아서 익히고 내가 긁어온 코드도 읽어보고 틀린 코드인지 아닌 코드인지 읽어보고 쓰는게 어떨까?