암호화와 복호화

  • 평문(plaintext)를 암호문(ciphertext)로 변환하는 과정을 암호화라 한다. 반대의 경우는 복호화라 한다.
  • 암호화 키와 복호화 키가 같다면 대칭키 암호라 한다. 반대의 경우는 비대칭 키 암호라 한다.
  • 암, 복호화 과정에서 메시지를 블럭 단위로 나누어 처리하는 것을 블럭 암호, 비트 단위로 처리하는 것을 스트림 암호라 한다.

블럭 암호 운용 방식

  • 블럭 암호 운용 방식은 하나의 키와 초기화벡터로 블럭 암호를 반복적으로 안전하게 이용하는 절차를 말한다.
  • 키(key)는 암호화, 복호화 시에 사용되며 128비트, 256비트와 같이 알고리즘에서 요구하는 길이를 사용해야 하므로 16바이트 또는 32바이트가 되어야 한다.
  • 일반적으로 ASCII 환경에서는 영어, 숫자, 기타 기호등은 한 글자당 1바이트이며, 한글은 2바이트다.
  • 그러나 UTF-8 환경에서는 영어, 숫자, 기타 기호는 똑같이 1바이트이며, 한글은 3바이트가 된다.
  • 초기화벡터(Initialization Vector, IV) 는 첫 블럭을 암호화할때 사용되는 값을 의미한다. (CBC 모드에서 가장 첫 블럭은 1단계 앞의 암호문 블럭이 존재하지 않기 때문)
  • CBC 모드에서는 평문이 반드시 1단계 앞의 암호문과 XOR 연산 후 암호화 된다. AND, OR, XOR 비트연산
  • IV는 키와 마찬가지로 송수신자 사이에 미리 약속되어 있어야 하지만 공개된 값을 사용해도 무방하다.
  • IV는 반복되어 사용되어서는 안된다. IV가 같은 경우 비슷한 두개의 평문을 암호화했을 때 앞부분의 블록들이 서로 같게 되어 보안 문제가 발생하기 때문이다. 때문에 암호화 때마다 랜덤 비트열을 사용하는 것이 보통이다.
  • 블럭 암호의 경우 비트 블럭 단위로 나누기 때문에 마지막 블럭이 기준 비트를 만족하지 못하는 경우를 채워줘야 한다. 이를 패딩이라고 한다. (ex. 300비트를 128비트 단위로 나누는 경우 128 + 128 + 44 이기 때문에 마지막 44 블럭을 128비트에 맞춰 패딩해야한다.)

SEED 128과 AES 128 블럭 암호 알고리즘

  • SEED 128은 KISA(한국인터넷진흥원)에서 개발했다.
  • AES 128은 벨기에 인에 의해 개발됐으며 미국을 비롯한 나라에서 사용중이다.
  • 두 알고리즘 모두 128비트 블럭 단위로 동작된다. (192, 256비트도 지원)
  • 두 알고리즘 모두 대칭키 암호다.

CryptoSwift

  • Swift에서 AES 128 암호 알고리즘 사용을 위해 CryptoSwift를 사용할 수 있다.
import CryptoSwift

let key = "p a s s w o r d ".bytes // 16바이트의 키 생성
let iv = "d r o w s s a p ".bytes // 16바이트의 초기화벡터 생성
let plaintext = "안녕하세요. 1급 비밀을 전달합니다. 블라블라~"

// ECB 모드
do {
    // 암호화
    let encrypted = try AES(key: key, blockMode: .ECB, padding: .pkcs7).encrypt(plaintext.bytes)
    print(encrypted) // [160, 48, 156, 182, 178, 99, 10, 81, 132, 75, 65, 167, 151, 163, 131, 118, 252, 184, 125, 76, 106, 241, 152, 29, 51, 113, 88, 10, 2, 245, 37, 152, 16, 96, 137, 91, 243, 30, 157, 63, 86, 157, 30, 80, 11, 199, 127, 35, 102, 80, 31, 241, 128, 128, 187, 248, 100, 17, 98, 44, 209, 96, 230, 204]
    print(encrypted.toHexString()) // a0309cb6b2630a51844b41a797a38376fcb87d4c6af1981d3371580a02f525981060895bf31e9d3f569d1e500bc77f2366501ff18080bbf86411622cd160e6cc
    
    // 복호화
    let decrypted = try AES(key: key, blockMode: .ECB, padding: .pkcs7).decrypt(encrypted)
    print(decrypted) // [236, 149, 136, 235, 133, 149, 237, 149, 152, 236, 132, 184, 236, 154, 148, 46, 32, 49, 234, 184, 137, 32, 235, 185, 132, 235, 176, 128, 236, 157, 132, 32, 236, 160, 132, 235, 139, 172, 237, 149, 169, 235, 139, 136, 235, 139, 164, 46, 32, 235, 184, 148, 235, 157, 188, 235, 184, 148, 235, 157, 188, 126]
    print(String(bytes: decrypted, encoding: .utf8)!) // 안녕하세요. 1급 비밀을 전달합니다. 블라블라~
} catch {
    print(error)
}

// CBC 모드
do {
    // 암호화
    let encrypted = try AES(key: key, blockMode: .CBC(iv: iv), padding: .pkcs7).encrypt(plaintext.bytes)
    print(encrypted) // [187, 181, 61, 114, 215, 25, 208, 113, 242, 222, 198, 94, 250, 255, 108, 195, 230, 76, 51, 224, 220, 231, 116, 144, 240, 253, 48, 174, 33, 240, 238, 228, 190, 29, 215, 133, 139, 126, 54, 96, 132, 18, 230, 253, 43, 49, 124, 121, 166, 240, 76, 9, 160, 156, 63, 248, 219, 40, 184, 194, 99, 33, 83, 34]
    print(encrypted.toHexString()) // bbb53d72d719d071f2dec65efaff6cc3e64c33e0dce77490f0fd30ae21f0eee4be1dd7858b7e36608412e6fd2b317c79a6f04c09a09c3ff8db28b8c263215322
    
    // 복호화
    let decrypted = try AES(key: key, blockMode: .CBC(iv: iv), padding: .pkcs7).decrypt(encrypted)
    print(decrypted) // [236, 149, 136, 235, 133, 149, 237, 149, 152, 236, 132, 184, 236, 154, 148, 46, 32, 49, 234, 184, 137, 32, 235, 185, 132, 235, 176, 128, 236, 157, 132, 32, 236, 160, 132, 235, 139, 172, 237, 149, 169, 235, 139, 136, 235, 139, 164, 46, 32, 235, 184, 148, 235, 157, 188, 235, 184, 148, 235, 157, 188, 126]
    print(String(bytes: decrypted, encoding: .utf8)!) // 안녕하세요. 1급 비밀을 전달합니다. 블라블라~
} catch {
    print(error)
}



// 간소화 모드 (CBC 알고리즘)
do {
    // 암호화
    let aes = try AES(key: "p a s s w o r d ", iv: "d r o w s s a p ")
    let encrypted = try aes.encrypt("안녕하세요. 1급 비밀을 전달합니다. 블라블라~".bytes)
    print(encrypted) // [187, 181, 61, 114, 215, 25, 208, 113, 242, 222, 198, 94, 250, 255, 108, 195, 230, 76, 51, 224, 220, 231, 116, 144, 240, 253, 48, 174, 33, 240, 238, 228, 190, 29, 215, 133, 139, 126, 54, 96, 132, 18, 230, 253, 43, 49, 124, 121, 166, 240, 76, 9, 160, 156, 63, 248, 219, 40, 184, 194, 99, 33, 83, 34]
    print(encrypted.toHexString()) // bbb53d72d719d071f2dec65efaff6cc3e64c33e0dce77490f0fd30ae21f0eee4be1dd7858b7e36608412e6fd2b317c79a6f04c09a09c3ff8db28b8c263215322
    
    // 복호화
    let decrypted = try aes.decrypt(encrypted)
    print(decrypted) // [236, 149, 136, 235, 133, 149, 237, 149, 152, 236, 132, 184, 236, 154, 148, 46, 32, 49, 234, 184, 137, 32, 235, 185, 132, 235, 176, 128, 236, 157, 132, 32, 236, 160, 132, 235, 139, 172, 237, 149, 169, 235, 139, 136, 235, 139, 164, 46, 32, 235, 184, 148, 235, 157, 188, 235, 184, 148, 235, 157, 188, 126]
    print(String(bytes: decrypted, encoding: .utf8)!) // 안녕하세요. 1급 비밀을 전달합니다. 블라블라~
} catch {
    print(error)
}

SEED 128

...
Objective-C Bridging Header.h
...

import CryptoSwift

// SEED 사용
var key = "passwordpassword".bytes // 16바이트의 키 생성
var iv = "passwordpassword".bytes // 16바이트의 초기화벡터 생성
var plaintext = "안녕하세요.".bytes // 암호화될 평문 생성 (16바이트로 생성됨)

var ciphertext = "                                ".bytes // 출력버퍼 생성 (입력버퍼보다 16바이트 크게 확보하는 것이 좋음 -> 암호화될 평문(입력 버퍼): 16바이트 // 출력될 암호문(출력 버퍼): 32바이트
print(ciphertext) // [32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32]
var plaintext_ = "                                ".bytes // 복호화시 필요한 출력버퍼 생성 (위와 동일하게 16바이트 추가 확보)
print(plaintext_) // [32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32]



// 암호화
// KISA_SEED_CBC_ENCRYPT 함수는 암호화가 성공하면 암호문의 길이를 리턴해준다. 암호문은 ciphertext 변수에 바로 적용된다. 왜냐? & 연산자를 썼기 때문.
var outlen = KISA_SEED_CBC_ENCRYPT(&key, &iv, &plaintext, 16, &ciphertext) // C로 된 함수가 포팅되기 때문에 UnsafeMutablePointer<UInt8> 이 인자값으로 쓰인다. 하지만 Swift에서는 포인터 개념이 없기 때문에 & 연산자를 통해 메모리 주소값을 바로 보낼 수 있다. (이 SEED 함수 쓰려고 4일동안 찾아다니다 혼자 시도해본게 성공 ㅡ.ㅡ 아무도 안알려주고 어디에도 정보가 없다...)
print(ciphertext) // [18, 69, 23, 167, 212, 215, 104, 251, 46, 225, 184, 4, 97, 49, 195, 181, 130, 108, 249, 15, 28, 183, 249, 111, 176, 64, 134, 20, 161, 159, 32, 10]
// 처음과 다르게 ciphertext의 바이트가 변경된 것을 알 수 있다! -> 고로 암호문이 입력되었음을 알 수 있다.

// 복호화
outlen = KISA_SEED_CBC_DECRYPT(&key, &iv, &ciphertext, UInt32(outlen), &plaintext_)
print(plaintext_) // [236, 149, 136, 235, 133, 149, 237, 149, 152, 236, 132, 184, 236, 154, 148, 46, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]
// 마찬가지로 plaintext_의 바이트가 처음과 다름을 알 수 있다! -> 고로 암호문이 복호화 되었음을 알 수 있다.

// 마지막 작업으로 복호화된 문장의 패딩을 셀프로 제거해야한다 ㅡ.ㅡ...
let count = Int(outlen) // outlen은 UInt32기 때문에 편의를 위해 Int로 변경. (참고로 outlen은 아까 말했듯 암,복호화 성공시 암호문의 길이를 리턴해준다. 여기서 Outlen은 16, 아까 암호화했을땐 outlen이 32였다.)
var removedPadding = [UInt8]() // 패딩이 제거된 바이트들이 저장될 UInt8 배열 생성

// 패딩은 제거하고 실제 필요한 바이트들만 배열에 담는다.
for i in 0...(count - 1) {
    removedPadding.append(plaintext_[i])
}

// 패딩이 제거된 바이트들을 인코딩하면 완성!!!
print(String(bytes: removedPadding, encoding: .utf8)!) // 안녕하세요.

BIP 0039

  • BIP는 결정론적 지갑을 생성하기 위해 연상 기호 코드나 문장을 실행하는 것이라 할 수 있다.
  • 결정론적 지갑은 Seed라고 하는 단일 시작 지점에서 키를 파생시키는 시스템이다.
  • 이 Seed는 별다른 정보 요구 없이 지갑을 쉽게 백업하고 복원할 수 있게 한다.
  • Seed는 어떤 경우에는 개인키에 대한 정보없이 공개키를 만들 수 있게 해주기도 한다.
  • Seed는 일반적으로 사람이 읽을 수 있는 연상 기호 코드로 연속된다.
  • 연상 기호 코드는 2진수 또는 16진수 처리와 비교해서 종이에 쓰거나 말로 할 수 있을 정도로 사람과의 상호작용에 우월하다. (ex. 한국 -> kor, 25인치 검정색 TV -> 25-BL-TV)
  • BIP는 연상 기호를 생성하는 부분과 이진법 시드로 변환하는 두 가지로 나뉘며, 시드가 나중에 결정성 지갑을 생성하는데 사용될 수 있다.

비트연산

  • Swift로 따지면 &&(AND), ||(OR) 이 있으며 비트연산시에는 &(AND), |(OR), ^(XOR)로 사용된다.
// 비트연산시 결과

// AND 연산자는 모든 입력값이 1인 경우에만 1을 출력
0 & 0 // 0
0 & 1 // 0
1 & 0 // 0
1 & 1 // 1

// OR 연산자는 입력값 중 1이 있다면 1을 출력
0 | 0 // 0
0 | 1 // 1
1 | 0 // 1
1 | 1 // 1

// XOR 연산자는 입력값이 같지 않다면 1을 출력
0 ^ 0 // 0
0 ^ 1 // 1
1 ^ 0 // 1
1 ^ 1 // 0