해시함수는 데이터의 효율적 관리를 목적으로 임의의 길이의 데이터를 항상 같은 길이의 데이터로 매핑하는 함수다. 이 때 매핑 전 원래 데이터의 값을 키(key),
매핑 후 데이터의 값을 해시값(hash value) 또는 해시코드, 체크섬 등이라고 부르고,
매핑하는 과정 자체를 해싱(hashing)이라고 한다.
![Untitled]
해시함수는 결과값을 가지고 원래의 원본 데이터를 알아내기 힘들다는 특징이 있어서 암호화 할 때 사용한다.
가장 많이 사용하는 해시함수는 MD5와 SHA 계열이다. 둘 다 암호학적 해시함수이다.
비암호학적 해시함수로는 CRS32 등이 있다.
미국 NIST에서 제정한 표준
95년도에 미국 국가안보국NSA에서 SHA-0을 개정한 SHA-1을 발표했다.
해시값의 길이는 20바이트다. : https://www.convertstring.com/ko/Hash/SHA1
충돌이 발견되어 더 이상 암호 용도로는 사용을 제한한다.
2017년에 SHA-1 충돌을 현실화 시킨 구글
2005년에 이론적 공격 방법이 알려졌고, 2011년에 NIST에 의해 공식적으로 폐기됐다.
그 이후 SHA-256이나 SHA-3으로 많이 전환됐지만 아직 SHA-1을 쓰는 경우도 있다고 한다.
신용카드 거래, 전자문서, 오픈소스 저장소, 소프트웨어 업데이트, 디지털 인증서, 백업시스템,
Git, SVN, 등이 SHA-1 잠재적으로 취약점에 노출되어 있다
미국 국가 안보국이 설계한 암호화 해시 함수들의 집합이다.
해시값의 길이가 SHA-1보다 더 길다.
SHA-2 계열은 224, 256, 384, 512비트로 된 해시값이 있는 6개의 해시 함수를 구성하고 있다 :
SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256
미국 국립표준기술연구소 NIST에서는 기존의 SHA-1, SHA-2 와는 구조와는 완전히 다른 해시 함수를 공모를 시작했다. 5년 이상에 걸쳐 진행된! 공모를 통해 12년도에 최종적으로 Keccak (케착)의 해시 함수를 SHA-3 로 선정하여 2015년에 발표했다.
비교적 느리다는 단점이 있다.
1992년 RFC 1321로 정의된 128비트 암호화 해시함수다.
충돌이 발견되어 더 이상 암호 용도로는 사용을 제한한다.
다음은 안전한 해시함수가 되기위해서 충족해야하는 조건들이다.
1. 역상 저항성 해시 값을 생성하는 입력값을 찾는 것이 계산상 어려워야한다. ****
(역상 : hash 전 데이터 원본, 저항성: 내성)
2. 제 2역상 저항성 동일한 해시값이 나오는 다른 입력값을 찾는 것은 계산적으로 불가능해야한다. ****
3. 충돌 저항성 동일한 해시 값이 나와서는 안된다.
해시함수를 사용하여 만들어낼 수 있는 값들을 대량으로 저장한 표다.
해시함수를 이용하여 저장된 비밀번호로부터 원래의 비밀번호를 추출해 내는데 사용된다.
촵촵
기존에는 패스워드만 해시함수에 통과시켰다. 최근에는 가입 시간이나 난수를 비밀번호와 같이 해시값에 포함시킨다. 이 때 추가적으로 포함된 가입 시간이나 난수를 솔트(salt)라고 한다.
서로 다른 계정이 같은 비밀번호를 사용하더라도 솔트가 다르면 완전 다른 해시값이 생성되기에 레인보우테이블에 의한 암호 크랙 공격을 어느정도 무력화 시킬 수 있다.
데이터베이스에 저장된 사용자 계정의 해시값 등을 꺼내온 다음에,
이것들이 사용자의 암호를 복잡한 알고리즘으로 계산한 값과 일치하는지 확인하는 과정 등을 거쳐야 한다.
아이디와 비밀번호를 크롬에 저장해서 댓글 달기 등 인가가 필요할 때 아이디와 비밀번호를 보내주는 방식은 로그인이 위처럼 무거운 작업이라 매번 하기 어렵기도 하고 매 요청 마다 아이디와 비밀번호가 날아다니면 보안상 위험하기도 하다.
그래서 우리는 세션방식을 사용했다.
(출처 당근마켓)
사용자가 로그인에 성공하면 서버는 세션랜드 이용권을 출력한다. 반은 브라우저(클라이언트), 반은 서버의 메모리(또는 하드디스크, 데이터베이스)에 넣어둔다.
여기서 브라우저는 이 이용권을 세션아이디라는 이름의 쿠키(브라우저에 저장되는 정보)로 저장한다.
세션랜드 놀이기구를 이용하기 위해서는 인가가 필요하다.
요청에 브라우저의 이용권 = 세션 아이디라는 쿠키가 실려오면 서버는 그걸 메모리에서 맞는 짝이 있는지 찾아가고 있으면 인가(authorization) 해준다.
이렇게 세션 아이디를 이용해서 서버에 로그인 되어 있음이 지속되는 상태를 세션이라고 한다.
세션 방식의 단점
메모리부족 : 인가가 필요할 때마다 서버의 메모리에 올려둔 세션객체와 매번 비교하다보면 사용자 동접이 많아질 때 메모리가 부족해질 수 있음
느린 속도 : 서버 접근 시간은 책상=메모리 / 서랍=하드디스크 / 창고=데이터베이스 에 다녀가는 것 처럼 시간이 걸린다. 하드디스크나 데이터베이스에 넣어두면 접근 속도가 느리다.
이를 해결하기 위해 레이스나 Mdm Cached같은 메모리형 데이터베이스 서버를 사용할 수 있지만 구현하기 어렵다고한다.
이런 인가를 부담 없이 구현하기 위해 토큰방식이 생겼다. 토큰 기반 인증 구현 시 가장 대표적인 기술인 JWT(Json Web Token)에 대해 알아보자.
출처 당근마켓
사용자가 로그인하면 토큰이라는 표를 출력해서 건네준다. 반띵 안하고 통째로 줘서 서버에서 기억할건 없다.
JWT는 인코딩 또는 암호화된 세가지 데이터를 이어 붙인 것이다.
마침표를 기준으로 세 부분으로 나뉘는데 header, payload, signature로 구분된다.
const header = {
"typ": "JWT",
"alg": "HS256"
};
//공백은 사라짐
// encode to base64
const encodedPayload = new Buffer(JSON.stringify(payload))
.toString('base64')
.replace('=', '');
console.log('payload: ',encodedPayload);
/* Result:
header: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
*/
payload에는 토큰에 담을 정보가 들어있다. 페이로드에 담긴 정보의 한 조각을 클레임이라고 부른다.
이 클레임은 name, value의 한 쌍으로 이루어져 있고 토큰에는 여러개의 클레임을 넣을 수 있다.
클레임은 닉네임, 관리자 여부 등 서비스 측에서 원하는 대로 담을 수 있다.
로그인, 인가 시 마다 사용자로부터 서버에게 이런 정보들이 보내져서 서버가 요청마다 데이터베이스에서 뒤져봐야 할 것들이 줄어든다.
const payload = {
"iss": "velopert.com",
"exp": "1485270000000",
"https://velopert.com/jwt_claims/is_admin": true,
"userId": "11028373727102",
"username": "velopert"
};
// encode to base64
const encodedPayload = new Buffer(JSON.stringify(payload))
.toString('base64')
.replace('=', '');
console.log('payload: ',encodedPayload);
/* result
payload: eyJpc3MiOiJ2ZWxvcGVydC5jb20iLCJleHAiOiIxNDg1MjcwMDAwMDAwIiwiaHR0cHM6Ly92ZWxvcGVydC5jb20vand0X2NsYWltcy9pc19hZG1pbiI6dHJ1ZSwidXNlcklkIjoiMTEwMjgzNzM3MjcxMDIiLCJ1c2VybmFtZSI6InZlbG9wZXJ0In0
*/
base64로 인코딩 되어 있어서 조작할 수도 있는거 아닌가? 하는 생각이 들 수 있다. !! 조작을 방지하기 위해 헤더와 시그니쳐가 있다.
헤더, 페이로드, 서버에 감춰논 비밀 값(솔트)을 암호화 알고리즘에 넣고 돌리면 서명 값이 나온다.
암호화 알고리즘은 단방향이라서 서버만 가진 비밀값을 찾아 낼 방법이 없고, 따라서 조작된 토큰을 이용할 수 없다.
const crypto = require('crypto');
const signature = crypto.createHmac('sha256', 'secret')
.update(encodedHeader + '.' + encodedPayload)
.digest('base64')
.replace('=', '');
console.log('signature: ',signature);
/* result
signature: WE5fMufM0NDSVGJ8cAolXGkyB5RmYwCto1pQwDIqo2w
*/
서버는 사용자의 값을 다 가지고 있을 필요 없이 비밀 값(솔트)만 가지고 있으면 토큰을 돌려보고 서명값이 같은지 확인만 하면 된다.
서명값과 1,2번 계산 값이 일치하고, 유효기간도 지나지 않았다면 로그인 된 회원으로서 인가를 받는다.
인증 상태를 관리하는 주체가 서버가 아니므로, 토큰이 탈취되어도 해당 토큰을 강제로 만료시킬 수 없다. 따라서 토큰이 만료될 때까지 사용자로 가장해 계속해서 요청을 보낼 수 있다.
서버에서 사용자의 상태를 기억할 수 없는 것이 무상태성이라고 생각하면 보다 간단하다.
시간에 따라 바뀌는 값을 가지지 않는걸 무상태성이라 한다.
토큰이 탈취되는 상황을 대비해서 유효 기간을 짧게 설정하면, 사용자는 토큰이 만료될 때마다 다시 로그인을 진행해야 하기 때문에 좋지 않은 사용자 경험을 제공한다. 그렇다고 유효 기간을 길게 설정하면 토큰이 탈취될 경우 더 치명적으로 작용할 수 있다.
토큰에 여러 정보를 담을 수 있는 만큼, 많은 데이터를 담으면 그만큼 암호화하는 과정도 길어지고 토큰의 크기도 커지기 때문에 네트워크 비용 문제가 생길 수 있다.
토큰 방식은 서버에서 상태를 기억할 수 없으니 통제할 수도 없다. 따라서 PC, 폰, 전자기기에서 동시로그인도 가능해진다. 이를 보완하기 위해 Access, Refresh 토큰이 있다.
Access토큰
액세스 토큰은 말 그대로 서버에 접근하기 위한 토큰으로 앞서 다룬 토큰과 비슷한 역할을 한다. 따라서 보안을 위해 보통 24시간 정도의 짧은 유효기간이 설정되어 있습니다.
Refresh토큰
리프레시 토큰은 서버 접근을 위한 토큰이 아닌 액세스 토큰이 만료되었을 때 새로운 액세스 토큰을 발급받기 위해 사용되는 토큰이다. 따라서 리프레시 토큰은 액세스 토큰보다 긴 유효기간을 설정한다.
서버의 데이터베이스에 리프레시토큰도 갖고있다가 사용자의 리프레시토큰과 맞으면 새 엑세스 토큰 보내주는 방식이다. 이러면 리프레시 토큰만 안전하게 관리된다면 탈취로부터 조금 더 안전해진다.
하지만 엑세스토큰이 살아있는 동안은 사용자를 차단할 순 없고, 리프레시 토큰이 탈취 될 위험도 있다.
참고자료
https://www.expressvpn.com/blog/salt-hash-password-protection/
https://egg-stone.tistory.com/19