본문 바로가기

etc

[암/복호화] AES128을 이용한 Java와 Python 간 암/복호화

같은 언어끼리 암/복호화를 하는 경우는 몇번 경험을 해보았지만, 언어가 다른 환경에서 암/복호화 하는것은 많은 경험이 없어 이것에 대해 글을 남겨보려고 한다.

0. 환경

  • Java 1.8
  • Python 3.7
    • pycrypto==2.6.1
  • 암호화방식: AES128

1. Python에서의 암/복호화

## aes128_crypto.py

import base64
from Crypto import Random
from Crypto.Cipher import AES

class AES128Crypto:

    def __init__(self, encrypt_key):
        self.BS = AES.block_size
        ##암호화 키중 16자리만 잘라서 쓴다.
        self.encrypt_key = encrypt_key[:16].encode(encoding='utf-8', errors='strict')
        self.pad = lambda s: bytes(s + (self.BS - len(s) % self.BS) * chr(self.BS - len(s) % self.BS), 'utf-8')
        self.unpad = lambda s: s[0:-ord(s[-1:])]

    def encrypt(self, raw):
        raw = self.pad(raw)
        # initialization vector 를 매번 랜덤으로 생성 한다.
        iv = Random.new().read(self.BS)
        cipher = AES.new(self.encrypt_key, AES.MODE_CBC, iv)

        # 암호화시 앞에 iv와 암화화 값을 붙여 인코딩 한다.
        # 디코딩시 앞에서 BS(block_size) 만금 잘라서 iv를 구하고, 이를통해 복호화 한다.
        return base64.b64encode(iv + cipher.encrypt(raw)).decode("utf-8")

    def decrypt(self, enc):
        enc = base64.b64decode(enc)

        # encrypt 에서 작업한 것처럼 첫 16바이트(block_size=BS) 를 잘라 iv를 만들고, 그 뒤를 복호화 하고자 하는 메세지로 잘라 만든다.
        iv = enc[:self.BS]
        encrypted_msg = enc[self.BS:]
        cipher = AES.new(self.encrypt_key, AES.MODE_CBC, iv)
        return self.unpad(cipher.decrypt(encrypted_msg)).decode('utf-8')

Python 모듈 사용방법

from cipher.aes128_crypto import AES128Crypto

if __name__ == '__main__':
    encrypt_key = "1234567890123456"
    plain_text = "서울특별시"

    cipher = AES128Crypto(encrypt_key)

    enc_text = cipher.encrypt(plain_text)
    dec_text = cipher.decrypt(enc_text)

    print(enc_text)
    print(dec_text)
    print(plain_text == dec_text)

2. Java에서의 암/복호화


package com.tistory.louisdev;

public interface Crypto {
    String encrypt(String message) throws Exception;
    String decrypt(String encryptedMessage) throws Exception;
}

package com.tistory.louisdev;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;

public class AES128Crypto implements Crypto{
    //Initialization Vector Block Size
    private static final int BLOCK_SIZE = 16;
    //암호화 키 사이즈
    private static final int MINIMUM_ENCRYPT_KEY_SIZE = 16;
    //암호화 알고리즘
    private static final String ALGORITHM = "AES";
    private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";

    private final byte[] encryptKeyBytes;

    public AES128Crypto(String encryptKey) {
        if(encryptKey == null || encryptKey.length() < MINIMUM_ENCRYPT_KEY_SIZE) {
            throw new RuntimeException(String.format("암호화 키는 최소 %d자리 이상이어야 합니다.", MINIMUM_ENCRYPT_KEY_SIZE));
        }

        //암호화키를 16자리만 자른 후 사용한다.
        encryptKeyBytes = encryptKey.substring(0, MINIMUM_ENCRYPT_KEY_SIZE).getBytes(StandardCharsets.UTF_8);
    }

    /**
     * 암호화
     * */
    public String encrypt(String message) throws Exception {
        byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);

        SecretKeySpec keySpec = new SecretKeySpec(encryptKeyBytes, ALGORITHM);
        byte[] ivBytes = createRandomIv();
        IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);

        Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);

        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        byte[] inputBytes = new byte[BLOCK_SIZE + messageBytes.length];

        //iv 와 Plain Text를 합쳐서 암호화 한다.
        System.arraycopy(ivBytes, 0, inputBytes, 0, BLOCK_SIZE);
        System.arraycopy(messageBytes, 0, inputBytes, BLOCK_SIZE, messageBytes.length);
        return Base64.encodeBase64String(cipher.doFinal(inputBytes));
    }

    /**
     * 복호화
     * */
    public String decrypt(String encryptedMessage) throws Exception {
        byte[] msgBytes = Base64.decodeBase64(encryptedMessage);

        //암호화된 메세지 자체가 (iv + 메세지)로 되어있기 때문에 앞에서 부터 BLOCK_SIZE 만큼 잘라내어 iv를 만들고
        //나머지 부분을 잘라내서 복호화한다.
        byte[] ivBytes = Arrays.copyOfRange(msgBytes, 0, BLOCK_SIZE);
        byte[] inputBytes = Arrays.copyOfRange(msgBytes, BLOCK_SIZE, msgBytes.length);

        IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec keySpec = new SecretKeySpec(encryptKeyBytes, ALGORITHM);
        Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        return new String(cipher.doFinal(inputBytes));
    }

    /**
     * Create Random Initialization Vector
     * */
    private byte[] createRandomIv(){
        SecureRandom random = new SecureRandom();
        byte[] ivBytes = new byte[BLOCK_SIZE];
        random.nextBytes(ivBytes);

        return ivBytes;
    }
}

Java 모듈 사용방법

package com.tistory.louisdev;

import org.junit.Assert;
import org.junit.Test;

public class AES128CryptoTest {
    private static final String SECRET_KEY = "1234567890123456";

    @Test
    public void testAES128Crypto() throws Exception {
        Crypto crypto = new AES128Crypto(SECRET_KEY);
        String plainText = "서울특별시";

        String encryptText = crypto.encrypt(plainText);
        String decryptText = crypto.decrypt(encryptText);

        System.out.println("Encrypt Text : " + encryptText);
        System.out.println("Decrypt Text : " + decryptText);

        Assert.assertEquals(plainText, decryptText);
    }
}

암호화키(Secret Key)를 동일하게 하면 Java 암호화 -> Python 복호화, Python 암호화 -> Java 복호화가 가능하다.