Open Hminchae opened 7 months ago
대칭키(Symmetric key)방식은 암호문을 생성(암호화)할 때 사용하는 키와, 암호문으로부터 평문을 복원(복호화)할 때 사용하는 키가 동일한 암호 시스템으로 일반적으로 알고 있는 암호 시스템
하나의 키로 암호화와 복호화를 수행하므로 대칭키(Symmetric key) 또는 비밀키(Secret Key)방식의 암호화라고 함
대칭키의 종류
장점
Windows의 BitLocker와 OS X의 FileVault 같이 디스크 전체를 암호화할 대도 대칭키 방식(AES)를 쓴다..!
하나의 키만 사용하므로 상대방과 대칭키 기반으로 암호화 통신을 할 경우 상대방도 사전에 같은 키를 가지고 있어야 함 -> 암호화 통신을 위한 키 전달과 관리가 어려움..!
비대칭키 암호화는 다른 말로
공개키 암호 알고리즘
이라고 알려져 있음. 이는 하나의 키가 아닌 암호화에 쓰이는 키 값과 복호화에 쓰이는 키 값이 다른 암호 시스템
양방향 암호화
로, 복호화가 가능한 암호 알고리즘
공개키 암호는 대칭키 암호의 키 전달에 있어서 취약점을 해결하기위한 노력의 결과로 탄생
방식
개인키로 암호화 한 정보
는 그 쌍이 되는 공개키로만 복호화가 가능공개키로 암호화 한 정보
는 그 쌍이 되는 개인키로만 복호화가 가능
💡 만약 개인이 비밀통신을 할 경우 비대칭키 암호화보다 대칭키 암호를 사용할 수 있지만, 다수가 통신을 할 때에는 키의 개수가 급증하게 되어 큰 어려움이 따름. 이런 문제를 극복하기 위하여 나타난 것이 공개키 암호화이고 공개키 암호화는 다른 유저와 키를 공유하지 않더라도 암호를 통한 안전한 통신을 할 수 있음이 큰 장점임
비대칭키의 종류
대칭키 암호화 방식 | 비대칭키 암호화 방식 | |
---|---|---|
개념 | - 암호화(비밀키) = 복호화(비밀키) - 대칭구조 |
- 암호화(공개키)와 복호화(개인키)가 다르며 , 이들 중 복호화키만 비밀로 간직 - 비대칭 구조 |
특징 | - 대량의 Data 암호화 유리 | - 전자서명, 공인인증서 등 다양한 이용 |
장점 | - 연산 속도 빠르고 구현 용이 - 일반적으로 같은 양의 데이터를 암호화하기 위한 연산이 공개키 암호보다 현저히 빠름 - 쉽게 기밀성을 제공 |
- 키 분배/키 관리가 용이 - 사용자의 증가에 따라 관리할 키의 개수가 상대적으로 적음 - 키 변화의 빈도가 적음(공개키의 복호화키는 길고 복잡하기 때문) - 기밀성, 무결성을 지원하고 특히 부인 방지 기능을 제공 |
단점 | - 키 관리가 어려움 - 무결성 지원이 부분적으로만 가능하고, 부인방지 기능을 제공하지 않음 |
- 키의 길이가 길고 연산 속도가 느림 |
알고리즘 | SEED, HIGHT, ARIA, LEA, DES, AES, RC4 등 | RSA, Diffie- Hellman, ECC, digital signature |
메시지를 보낸 소혜가 자신의 개인키로 서명해서 메시지 보낸 사람이 소혜 본인임을 입증하는 것
메시지 송신자(A)가 메시지 수신자(B)한테 메시지를 전송하려고 한다.
A는 메시지를 A의 개인키로 암호화한 메시지를 만든다. (서명)
A는 메시지를 B의 공개키로 암호화한다.
B한테 암호화한 메시지와 서명문을 같이 전달한다.
B는 B의 개인키로 암호화한 메시지를 복호화한다.(암호화 메시지 -> 메시지)
또, B는 A의 공개키로 서명문을 복호화한다. (서명 -> 메시지')
메시지와 메시지'가 같다면, A가 메시지를 보내었다는 것이 보장된다.
애플은 암호화를 위해 CryptoKit 이라는 프레임워크 제공함
iOS 앱내에서 RSA 암호화 방식을 사용 하려면 두가지 방법이 있다. 방법에 대하여 자세한 설명은 여기 블로그에 잘 설명 되어 있슴다..
직접구현
암호화 작업을 안전하고 효율적으로 수행할 수 있도록 도와주는 애플 프레임워크 이 내용에 대해서는 아래 해시함수를 다루고 더 설명 드리겠음
let privateKey = Curve25519.Signing.PrivateKey() // 개인키 생성
let publicKey = privateKey.publicKey // 공개키 생성
let signature = try privateKey.signature(for: messageDigest) // 서명
publicKey.isValidSignature(signature, for: messageDigest) // 검증
해시함수(hash function)는 임의의 길이는 데이터
message
를 고정 길이의 데이터hash value
,hash code
,digest
,hash
로 매핑하는 함수임. 동일한 input에 대해서는 같은 결과를 도출한다!1) 암호화 해시 함수
- 해시함수의 일종, 해시값으로부터 원래의 입력값과의 관계를 찾기 어려운 성질을 가짐
- 역상 저항성(pre-image resistance) : 해시값이 주어졌을 때, 해시값을 생성한 입력값을 찾는 것이 계산상 어려움. 제 1 역상 공격(주어진 해시값을 출력하는 입력값을 찾는 공격)에 대해 안전한 것을 말함
- 제 2 역상 저항성(second pre-image resistance) : 해시값과 입력값이 주어졌을 때, 해시값을 생성하는 또 다른 입력값2를 찾는것이 계산상 어려움. 제 2 역상 공격(주어진 입력값과 같은 해시값을 출력하는 다른 입력값을 찾는 공격)에 대해 안전한 것을 말함
- 충돌 저항성(collision resistance) : 같은 해시값을 생성하는 두 개의 입력값을 찾는 것이 계산상 어려워야 하며 해시충돌에 대해 안전해야 한다는 것을 말함
- 여기서 제 2 역상 저항성과 충돌 저항성의 차이점은, 제 2 역상 저항성에서는 공격자가 입력값 외 동일한 해시값을 생성하는 입력값2를 찾는 것이고, 충돌 저항성은 공격자가 동일한 해시값을 생성하는 입력값 두개를 자유롭게 선택할 수 있음
- 해시충돌이란? 해시 함수가 서로 다른 두개의 입력값에 대해 같은 출력값을 도출하는 상황. 무한한 입력값을 받아 유한한 출력값을 생성하는 경우, 비둘기집 원리에 의해 해시충돌은 항상 존재한다..!
- 비둘기집 원리
- n + 1개의 물건을 n개의 상자에 넣을 때 어느 한 상자에는 두 개 이상의 물건이 들어 있다는 원리
- 5명의 사람을 서로 같은 팀이 되지 않도록 4개의 팀에 나누는 것은 불가능함
- 생일 문제
- 사람이 임의로 모였을 때 그 중에 생일이 같은 두명이 존재할 확률을 구하는 문제
- 1년은 365일이므로 366명이 모인다면 비둘기집의 원리에 따라 생일이 같은 두 명이 반드시 존재함
- 23명만 모여도 두 명이 생일이 같을 확률은 50%가 넘고, 57명이 모이면 99%를 넘어감
2) 순환 중복 검사(CRC, cyclic redundancy check)
- 네트워크 등을 통하여 데이터를 전송할 때, 전송된 데이터에 오류가 있는지 확인하는 방식
- 데이터를 전송하기 전, 주어진 데이터에 대한 CRC 값을 계산하여 데이터에 붙여 전송함
- 받은 데이터의 값으로 CRC 값을 계산해 오류가 덧붙여 전송되었는지 확인
- 주어진 CRC값을 가지는 다른 데이터를 만들기 쉬움 => 제 2 역상 저항성, 충돌 저항성을 가지지 않음
위 같은 해시함수에 사용되는 알고리즘으로는 MD(Message-Digest Algorithm)과 SHA(Secure Hash Algorithm)등이 있음. 각 알고리즘은 심각한 해시 충돌 문제 등으로 인해 해시 함수를 개선하며, 발표된 순서대로 MDn, SHA-n 식으로 넘버링됨
Xcode 내에서 제공하는 함수를 이용하여 MD5 또는 SHA256 해시를 생성할 수 있음. 그리고 이제 Apple이 지원하는 CrytoKit을 사용하여 다양한 암호화가 가능해졌음. 사용자의 비밀번호를 저장하는 방법에 평문으로 저장하기 보다는 해시 값을 이용하여 저장하고, 파일의 해시 값을 비교하여 파일의 무결성을 검증하는 등으로 응용할 수 있음
1) 대상 통으로 해시 생성
위 코드들은 CommonDigest.h 의 헤더파일의 일부이고 위 함수와 구조체를 이용하여 해시값을 구할 수 있음
CC_MD5함수를 이용하여 해시 값을 구할 수 있음 unsigned char CC_MD5(const void source, CC_LONG len, unsigned char *dest)
CC_MD5함수와 CC_SHA256함수는 unsigned char * 를 리턴하고 있으나, 일반적으로 아래 예시처럼 리턴 값을 사용하고 있지 않지만 리턴 값은 md파라미터를 통해 전달된 포인터를 반환함
#import <CommonCrypto/CommonDigest.h>
NSString *md5(NSString *str) {
const char *cStr = [str UTF8String];
unsigned char result[CC_MD5_Digest_LENGTH];
CC_MD5(cStr, strlen(cStr), result);
return [NSString stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
result[0], result[1],
result[2], result[3],
result[4], result[5],
result[6], result[7],
result[8], result[9],
result[10], result[11],
result[12], result[13],
result[14], result[15],
];
}
NSString *digest = md5(@"test");
NSLog(@"MD5 TEST %@", digest);
이 코드는 MD5 해싱소스 코드임. 여기서 MD5를 SHA256으로 치환하고, result의 배열인덱스를 0~ 31 까지 받아내면 SHA256소스가 된다.
예를 들어 크기가 큰 파일의 해시 값을 구한다고 가정할 때, 위의 방법을 이용하면 파일 전체를 메모리에 올려야 하기때문에 저사양 단말에서는 큰 부담을 가지게 될 수 있음! 따라서 파일의 일부를 버퍼를 이용하여 해시 값을 업데이트함으로써, 메모리사용량을 줄 일 수 있다고 함
static int md5useUpdate(char* data, long len, char** outData)
{
unsigned char hashBuf[BUF_LEN] = {0,}; //업데이트에 사용할 버퍼
long curSize = 0; //현재 까지 읽은 데이터 사이즈
long remainSize = len; //남아있는 데이터 사이즈
//md5 컨텍스트 선언 및 초기화
CC_MD5_CTX md5;
CC_MD5_Init(&md5);
//더 읽어야할 데이터가 남아있다면
while(curSize < len) {
if(remainSize < BUF_LEN){ //남은 사이즈가 버퍼보다 작을 경우, 남아있는 사이즈만큼만 메모리 복사 및 해시 업데이트
memcpy(hashBuf, (char *)data, remainSize);
CC_MD5_Update(&md5, hashBuf, (CC_LONG)remainSize);
}
else { //일반적인 경우 버퍼사이즈(1024)만큼 메모리 복사 및 해시 업데이트
memcpy(hashBuf, (char *)data, BUF_LEN);
CC_MD5_Update(&md5, hashBuf, (CC_LONG)BUF_LEN);
}
data += BUF_LEN; //메모리 읽은 후 포인터 이동
curSize += BUF_LEN; //현재까지 읽은 사이즈 계산
remainSize -= BUF_LEN; //남아있는 사이즈 계산
}
unsigned char digest[CC_MD5_DIGEST_LENGTH] = {0,};
CC_MD5_Final (digest, &md5);
char *tmpOut = *outData;
for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
{
sprintf(tmpOut, "%02x", digest[i]);
tmpOut += 2;
}
return 0;
}
정의한 함수 md5useUpdate는 해싱할 데이터와 사이즈, 해싱한 문자열을 기록할 데이터의 포인터를 입력받는다.
위 두가지방법을 예전엔 주로 사용한 것 같으나.. 이젠 Apple에서 지원한다.
/// 리터럴 값을 해싱하면 hash value이 나온다. /// haser로 만든 hash value는 계산할 때마다 다른 값이 나온다.
func hashItem(item: String) -> Int {
var hasher = Hasher()
item.hash(into: &hasher)
return hasher.finalize()
}
let hashvalue = hashItem(item: "brown fox")
digest로 데이터 무결성을 확인할 수 있음
import CryptoKit /// 이미지 데이터를 받아온다.
func getData(for item: String, of type: String) -> Data {
let filePath = Bundle.main.path(forResource: item, ofType: type)!
return FileManager.default.contents(atPath: filePath)!
}
let data = getData(for: "Baby", of: "png")
UIImage(data: data)
/// 256-bit의 digest를 만든다.
let digest = SHA256.hash(data: data)
// 송신자는 'data'와 'digest'를 수신자에게 보낸다.
// 수신자는 'data'를 cryptographic hashing한 값과 'digest'를 비교한다. 같다면 데이터는 무결한 상태이다.
let receivedDataDigest = SHA256.hash(data: data)
if digest == receivedDataDigest {
print("보낸 data == 받은 data")
}
data
와 Cryptographic hashing
의 결과 값 digest
와 같이 실어 보낸다.data
를 Cryptographic hashing
한 값과 전달받은 digest
를 비교한다. 값이 똑같으면 데이터에 손상 없고, 그렇지 않으면 손상이 있는 것CrytoKit이 제공하는 암호화나 인증방식이 꽤나 다양하기 때문에 여기에서 필요할 때 공부하는 걸 추천한다 ㅎㅎ