AES-256 에 관하여

HYUNGU, KANG·2020년 8월 10일
3

협력사에서 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 암호화 복호화에는 무엇이 필요할까? 크게 네가지가 있다.
(각 항목이 의미하는 바는 각자 알아서 찾아보시기를 바란다.)

  1. Secret key
  2. IV (Initialize Vector)
  3. Cipher Mode (CBC/ECB/...)
  4. Padding Mode (PKCS5/PCKS7/...)
    (참고로 자바에서는 PKCS5 패딩모드를 사용해도 PKCS7가 사용된다. Node.js 로 작성할때는 PKCS7 패딩을 구해와서 적용해주어야 한다.)

여기서 뭐 하나라도 다르면 결과가 다르게 나온다.
위에 네가지가 반드시 전달되어야 하는것이고

  • 그리고 보통 Base64 기반이니까, URL로 전달할거면 encodeURIComponent 로 인코딩할건지
    Base64 URL Safe 핸들링(base64 문자열의 + 와 / 를 - 와 _ 로 치환) 할건지 정해야 한다.

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. 인터넷에서 코드 긁어와서 쓰더라도, 쓸때는 개념이라도 찾아서 익히고 내가 긁어온 코드도 읽어보고 틀린 코드인지 아닌 코드인지 읽어보고 쓰는게 어떨까?

profile
JavaScript, TypeScript and React-Native

0개의 댓글