암호화, 복호화

박상훈·2022년 4월 6일
0
post-thumbnail

암호화(encryption)


평문(plaintext) 을 암호문(ciphertext) 으로 변환하는 것
누군가 볼 수 있지만 특별한 정보를 알아야 이해 가능

복호화(decryption)


암호문을 다시 평문으로 변환
암호화에 사용한 방법을 알아야 빠른 복호화 가능

해시 알고리듬과 암/복호화 차이


해시 알고리듬은 원문 복구를 막는게 목표
암호화 알고리듬은 원문 복구를 허용

대칭 키 암호화(symmetric-key encryption)


암/복호화 동일한 키 사용
키는 메시지 송신자, 수신자가 공유하는 비밀
송신자가 수신자에게 비밀스럽게 키를 전달하기 위한 방법 필요

스트림 암호(stream cipher)

한 번에 1 바이트씩 받아 암호화 진행
안전하려면 각 바이트에 적용하는 키가 달라야함(시드값 정하고 난수 생성)
블록 암호에 비해 설정은 복잡하며 속도는 빠르다

블록 암호(block cipher)

정해진 블록 크기(64bit 이상) 만큼의 바이트를 한 번에 암호화
각 블록에 사용하는 키가 동일
스트림 암호에 비해 설정은 간단하며 속도는 느리다

AES

NSA 에서 일급비밀 용으로 승인한 유일한 공개 암호화 알고리듬
블록 크기 : 128 비트
키 길이(라운드 길이) : 128(10), 192(12), 256(14) 비트

구성
키 확장
0 라운드 : 라운드 키 더하기
1 라운드 ~ 최종 라운드 - 1 : 바이트 대체, 행 이동, 열 섞기, 라운드 키 더하기
최종 라운드 : 바이트 대체, 행 이동, 라운드 키 더하기

키 확장
대칭 키로부터 각 라운드에 사용할 여러 키 생성(라운드 키)
총 라운드 수 + 1 개의 라운드 키를 만듦(각 라운드마다 다른 키 사용)
대칭 키 길이 128, 192, 256 비트(생성한 라운드 키 128 비트 고정)

바이트 대체
각 바이트를 다른 바이트로 대체
AES S-Box 라는 룩업 테이블을 사용(위키피디아에서 참조)
선형적인 변환이 아니라 단순 사칙 또는 비트 연산으로 원본 바이트를 찾을 수 없음

행 이동
4개의 행을 각각 다르게 왼쪽으로 이동
1행 : 이동 없음, 2행 : 1, 3행 : 2, 4행 : 3

열 섞기
각 열에 있는 4바이트를 선형적으로 변환
곱셈 규칙 - 1 곱하기
일반적인 곱셈과 동일, 값 그대로 유지
곱셈 규칙 - 2 곱하기
원래 값에 2를 곱함(왼쪽으로 1만큼 비트 시프트)
원래 값의 최고 비트가 1 인 경우 0x1B(0001 1011) 로 xor
곱셈 규칙 - 3 곱하기
x 3 = x 2 xor x

public static String encrypt(String plaintext, byte[] key) {
    try {
        byte[] plaintextInBytes = plaintext.getBytes(StandardCharsets.UTF_8);
        SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(new byte[16]));
        byte[] encryped = cipher.doFinal(plaintextInBytes);
        return Base64.getEncoder().encodeToString(encryped);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

public static String decrypt(String cipherText, byte[] key) {
    try {
        byte[] encrypted = Base64.getDecoder().decode(cipherText);
        SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(new byte[16]));
        byte[] plaintext = cipher.doFinal(encrypted);
        return new String(plaintext, StandardCharsets.UTF_8);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}


public static void main(String[] args) {
    String plaintext = "My message";
    String longPlaintext = "My longer message";

    {
        String aes128key = "1234567890123456";
        byte[] keyInBytes = aes128key.getBytes(StandardCharsets.US_ASCII);

        String ciphertext = encrypt(plaintext, keyInBytes);
        String longCiphertext = encrypt(longPlaintext, keyInBytes);

        System.out.println(ciphertext);
        System.out.println(longCiphertext);

        String decryptedText = decrypt(ciphertext, keyInBytes);
        String longDecryptedText = decrypt(longCiphertext, keyInBytes);

        System.out.println(decryptedText);
        System.out.println(longDecryptedText);
    }
}

비대칭 키 암호화(asymmetric-key encryption)


대칭 키 암호화의 문제점으로 암/복호화에 동일한 키를 사용해서 가끔 발목이 잡힘(메시지 교환, Wi-Fi)
보안 문제 없이 쉽게 키를 배포하는 방법 필요
암/복호화에 사용하는 키가 다름(두 키 사이에는 특수한 수학적 관계가 있음)
복호화에 사용할 키를 완전 공개(public key)
다른 키 하나는 개인이 비밀로 가지고 있음(private key)

올바른 메시지 암호화

송신자가 수신자의 공개 키로 원문을 암호화
키가 공개되어 있으니 누구나 메시지를 암호화하여 전달 가능
비공개 키를 가지고 있는 수신자만 자신의 메시지를 복호화하여 원문으로 변환 가능

비대칭 키 암호화 사용하는 곳

HTTPS, 비밀 채팅 모드, 암호화폐 프로토콜, Git 커밋 전자서명(GPG key), 등...

기법

디피-헬만 키 교환(Diffie-Helmal key exchange)
RSA(Rivest-Shamir-Adleman)
디지털 서명 알고리듬(Digital Signature Algorithm, DSA)
타원곡선 DSA(Elliptic Curve DSA, ECDSA)

RSA

현재 데이터 전송용으로 매우 널리 쓰이는 암호화 기법
정수론에 기초
공개/비밀 키 쌍을 만드는게 쉬움(매우 큰 두 소수 사용)
이 두 키는 특수한 수학적 관계를 가짐
평문을 거듭제곱 후 나머지 연산으로 암호화
암호문을 거듭제곱 후 나머지 연산하면 복호화

private static KeyPair getKeyPair() {
    try {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        generator.initialize(2048, new SecureRandom());
        return generator.generateKeyPair();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

private static String encrypt(String plaintext, PublicKey publicKey) {
    try {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] bytes = plaintext.getBytes(StandardCharsets.UTF_8);
        byte[] ciphertext = cipher.doFinal(bytes);
        return Base64.getEncoder().encodeToString(ciphertext);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

private static String decrypt(String ciphertext, PrivateKey privateKey) {
    try {
        byte[] bytes = Base64.getDecoder().decode(ciphertext);
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] plaintext = cipher.doFinal(bytes);
        return new String(plaintext, StandardCharsets.UTF_8);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

public static void main(String[] args) {
    KeyPair keyPair = getKeyPair();
    PublicKey publicKey = keyPair.getPublic();
    PrivateKey privateKey = keyPair.getPrivate();

    System.out.println(publicKey.toString());
    System.out.println(privateKey.toString());

    String plaintext = "My love letter";
    String ciphertext = encrypt(plaintext, publicKey);
    System.out.println(ciphertext);

    String actualPlaintext = decrypt(ciphertext, privateKey);
    System.out.println(actualPlaintext);
}

대칭 키, 비대칭 키 암호화의 속도


비대칭 키 암호화가 보통 더 느림
키 길이가 훨씬 길며 알고리듬 자체가 더 복잡하다 (하나의 키를 공개하기 위한 더 복잡한 설정)
비대칭/대칭 키 암호화를 같이 사용하기도 한다

profile
엔지니어

0개의 댓글