사전 SHA-256
알고리즘

SHA-256

gabury1고친 사람 github-actions[bot]

SHA-256 은 크기가 얼마든 상관없는 데이터를 짧은 값 하나로 줄여 그 데이터를 가리키는 이름으로 씁니다. 같은 데이터를 넣으면 언제나 같은 값이 나옵니다. 한 바이트만 달라져도 값이 전부 달라집니다. 그래서 파일이 오는 도중 망가졌는지 가려내는 데 씁니다.

쉽고 빠른 이해

SHA-256 은 어떤 데이터든 받아서 64글자 16진수 문자열 하나로 바꿔 줍니다. 파일 하나를 넣으면 그 파일을 가리키는 짧은 이름이 나오는 셈입니다.

이게 없으면 두 데이터가 같은지 보려고 처음부터 끝까지 맞대 봐야 합니다. 짧은 값으로 줄여 두면 값끼리만 견주면 됩니다.

어떻게 도나:

  1. 데이터 뒤에 정해진 규칙대로 채움 값을 붙여 길이를 맞춥니다
  2. 조각으로 쪼갠 뒤 하나씩 먹으며 내부 값을 계속 바꿉니다
  3. 조각을 다 먹고 남은 내부 값이 그 데이터의 SHA-256 값입니다

대가는 둘입니다. 나올 수 있는 값의 가짓수가 정해져 있어 서로 다른 데이터가 같은 값을 갖는 일까지 막지는 못합니다. 그리고 계산이 빨라서, 공격자가 후보를 마구 대입해 볼 수 있는 비밀번호 저장에는 그냥 쓰면 안 됩니다.

상세

SHA-256 은 Secure Hash Algorithm 256 의 줄임말입니다. 우리말로 옮기면 안전한 해시 알고리즘 256 입니다. 뒤의 256 은 출력 길이를 가리킵니다. 무엇을 넣어도 256비트짜리 값 하나가 나옵니다.

이런 함수를 해시 함수라고 부릅니다. 해시 함수는 길이가 제각각인 데이터를 짧은 값 하나로 줄입니다. 그 값을 해시값이라고 합니다.

그중에서 속이려는 사람이 있다고 보고 만든 것이 암호학적 해시 함수입니다. SHA-256 이 여기에 듭니다.

입력과 출력

입력에는 제한이 거의 없습니다. 짧은 문자열도 받고 큰 파일도 통째로 받습니다. 출력은 반대입니다. 입력이 한 바이트든 수 기가바이트든 나오는 값은 언제나 256비트입니다.

길이가 일정한 짧은 값이라 견주기도 옮기기도 쉽습니다. 큰 파일 둘이 같은지 보려고 전체를 맞대 보는 대신 값 하나씩만 견주면 됩니다. 해시 함수를 쓰는 까닭이 대개 여기 있습니다.

256비트는 32바이트입니다. 16진수로 적으면 한 바이트가 두 글자로 적히므로 64글자 문자열이 됩니다.

Python
h = sha256(b"hello")
h.digest()      # 32바이트
h.hexdigest()   # 64글자 문자열

앞줄은 바이트 그대로이고 뒷줄은 사람이 읽고 옮기기 좋게 16진수로 적은 것입니다. 둘은 같은 값입니다.

같은 입력에는 언제나 같은 값이 나옵니다. 되돌리는 길은 없습니다. 해시값에서 원래 데이터로 돌아가는 계산은 마련되어 있지 않고, 이 성질을 역상 저항성이라고 부릅니다.

SHA-256 은 혼자 있는 함수가 아니라 SHA-2(Secure Hash Algorithm 2) 라는 가족의 한 명입니다. 가족끼리 얼개는 같습니다. 다른 것은 출력 길이와 내부에서 다루는 값의 크기입니다.

함수 출력 비트 16진수 글자 수
SHA-224 224 56
SHA-256 256 64
SHA-384 384 96
SHA-512 512 128

이름 뒤의 숫자가 곧 출력 길이입니다. 출력이 길수록 뒤에서 볼 안전 여유가 커지지만 값을 저장하고 옮기는 비용도 같이 커집니다. 넷 가운데 SHA-256 이 가장 널리 쓰입니다.

어떻게 도나

SHA-256 은 데이터를 한꺼번에 계산하지 않습니다. 정해진 크기로 쪼개서 하나씩 먹습니다. 조각 하나는 512비트입니다.

먼저 길이를 맞춥니다. 데이터 끝에 정해진 규칙대로 채움 값과 원래 길이를 붙여 전체가 512비트의 배수가 되게 만듭니다. 이 작업을 패딩이라고 합니다.

block-beta
columns 3
  a["데이터"] b["채움 값"] c["원래 길이"]
  t["전체 길이 · 조각 512비트의 배수"]:3

붙는 순서가 정해져 있어 같은 데이터는 언제나 같은 모양으로 채워집니다.

그다음 256비트짜리 내부 값을 하나 놓고 시작합니다. 시작 값은 정해진 상수입니다. 조각을 하나 읽을 때마다 이 값을 섞어 새 값으로 바꿉니다. 섞는 계산은 한 조각당 예순네 번 되풀이됩니다.

flowchart TD
    IN["데이터 + 채움 값"] --> S0["시작 값 · 256비트"]
    S0 --> B1["조각 1 을 섞는다"]
    B1 --> B2["조각 2 를 섞는다"]
    B2 --> B3["마지막 조각을 섞는다"]
    B3 --> OUT["남은 내부 값 · 해시값"]

그림에서 볼 것은 내부 값 하나가 끝까지 이어진다는 점입니다. 조각마다 새로 시작하지 않고 앞 조각이 남긴 값 위에 다음 조각을 얹습니다. 이렇게 조각을 차례로 먹으며 내부 값을 갱신하는 얼개를 머클-담고르 구조라고 부릅니다. SHA-1(Secure Hash Algorithm 1)과 MD5(Message-Digest algorithm 5)도 같은 얼개를 씁니다.

섞는 계산에 바라는 성질이 눈사태 효과입니다. 입력을 한 비트만 바꿔도 출력 비트가 절반쯤 달라져야 합니다. 비슷한 입력이 비슷한 출력을 내면 출력을 보고 입력을 짐작할 실마리가 생깁니다.

무엇을 보장하나

출력 길이가 정해져 있으니 서로 다른 데이터가 같은 값을 갖는 일은 반드시 생깁니다. 이렇게 겹치는 쌍을 충돌이라고 합니다.

암호학적 해시 함수가 요구하는 것은 충돌이 없는 것이 아닙니다. 충돌하는 쌍을 계산으로 찾아내기가 현실적으로 불가능한 것입니다. 이 요구를 충돌 저항성이라고 부릅니다.

충돌하는 쌍을 찾기가 얼마나 어려운지는 출력 길이가 정합니다. 출력이 256비트면 값의 가짓수는 2의 256제곱입니다. 그래도 그 제곱근인 2의 128제곱 개쯤만 모으면 그중 두 개가 겹칠 확률이 반을 넘습니다. 이렇게 절반 지수에서 겹침이 터지는 현상을 생일 문제라고 합니다.

SHA-1 과 MD5 는 이 절반 지수보다도 훨씬 적은 계산으로 충돌을 만드는 방법이 밝혀져 서명 용도에서 물러났습니다. SHA-256 에는 그런 방법이 아직 알려져 있지 않습니다. 새로 고를 때 기본 후보가 되는 까닭입니다.

비밀을 앞에 붙여 인증표로 쓰지 않는다

메시지가 중간에 바뀌지 않았음을 보이려고 나만 아는 비밀 값을 메시지에 붙여 해시한 값을 같이 보내는 방식이 있습니다. 이 값을 인증표라고 합니다. 받는 쪽도 같은 비밀 값을 알고 있어 같은 계산을 해 보고 값이 맞는지 봅니다.

SHA-256 이 안 지켜 주는 것이 하나 있습니다. 해시값과 원래 데이터의 길이만 알면, 원래 데이터를 모르는 사람도 그 뒤에 무언가를 덧붙인 데이터의 해시값을 계산해 낼 수 있습니다. 이것을 길이 확장 공격이라고 부릅니다.

까닭은 해시값의 정체에 있습니다. 해시값은 마지막 조각을 먹고 남은 내부 값 그 자체입니다. 그 값을 손에 넣은 사람은 거기서 계산을 이어 가기만 하면 됩니다.

flowchart TD
    A["비밀 값 + 메시지 · 앞 조각들"] --> C["마지막 조각을 섞는다"]
    C --> D["해시값 · 남은 내부 값"]
    D --> E["정상 검증 · 비밀 값을 아는 쪽이 같은 계산을 다시 한다"]
    D --> F["공격자 · 해시값을 시작 값으로 놓고 덧붙인 데이터를 섞는다"]
    F --> G["덧붙인 메시지에 맞는 해시값"]

정상 검증은 비밀 값을 아는 쪽이 처음부터 다시 계산해 값을 맞춰 보는 길입니다. 공격자는 비밀 값을 몰라도 받은 해시값을 시작 값으로 놓고 덧붙인 데이터만 마저 섞으면 됩니다.

그래서 비밀 값과 메시지를 이어 붙여 해시한 값을 인증표로 쓰면 안 됩니다. 메시지를 못 읽는 공격자도 뒤에 내용을 덧붙이고 맞는 해시값을 함께 내밀 수 있습니다. 이런 용도에는 HMAC(Hash-based Message Authentication Code, 해시 기반 메시지 인증 코드)을 씁니다. 해시 함수를 두 겹으로 겹쳐 이 덧붙이기를 막는 방식입니다.

비밀번호에는 그냥 쓰지 않는다

비밀번호를 저장할 때 SHA-256 을 그냥 쓰면 안 됩니다. 이번에는 안전성이 아니라 속도가 흠입니다. SHA-256 은 빠르라고 만든 함수입니다. 공격자도 그만큼 빨리 후보를 대입해 볼 수 있습니다.

같은 비밀번호는 같은 해시값을 냅니다. 그래서 흔한 비밀번호의 해시값을 미리 계산해 둔 표가 있으면 털린 값을 한꺼번에 맞춰 볼 수 있습니다. 그런 표를 레인보우 테이블이라고 부릅니다.

이런 곳에는 일부러 계산이 오래 걸리게 만든 비밀번호 해싱 함수를 씁니다. 그리고 사용자마다 다른 임의 값을 함께 섞습니다. 그 값을 솔트라고 합니다. 솔트가 다르면 같은 비밀번호도 다른 값을 내므로 미리 만든 표가 더는 맞지 않습니다.

어디에 쓰고 어디에 안 쓰나

실무에서 마주치는 SHA-256 값은 대개 무결성 확인과 이름표입니다. 내려받은 파일 옆에 적힌 체크섬, 컨테이너 이미지를 가리키는 다이제스트(해시값을 이름으로 쓸 때 부르는 말), 머클 트리의 노드 값, Git 이 옮겨 가고 있는 새 객체 이름 체계가 그런 쓰임입니다.

지금까지 본 것을 쓰임별로 모으면 이렇습니다.

쓰임 SHA-256 으로 괜찮나
전송·저장 중 손상 확인 됩니다. 받은 값과 견주기만 하면 됩니다
내용으로 이름 붙이기 됩니다. 콘텐츠 주소 저장소가 이 방식입니다
서명할 대상 줄이기 됩니다. 서명 자체는 전자 서명 절차가 맡습니다
비밀 값을 앞에 붙인 인증표 안 됩니다. HMAC 을 씁니다
비밀번호 저장 안 됩니다. 비밀번호 해싱 함수를 씁니다

가르는 기준은 두 가지입니다. 값이 같은 가짜를 누가 일부러 만들 수 있는 상황인가. 그리고 계산이 금방 끝나는 것이 이 쓰임에 득인가 실인가.

표에 없는 쓰임을 만나면 이 순서로 묻습니다.

flowchart TD
    Q["이 쓰임에 SHA-256 을 쓸까"] --> A["값이 같은 가짜를 누가 일부러 만들 수 있나"]
    A -->|아니오| B["그대로 쓴다 · 손상 확인 · 이름표"]
    A -->|예| C["비밀 값을 앞에 붙여 인증표로 쓰나"]
    C -->|예| D["HMAC 을 쓴다"]
    C -->|아니오| E["공격자가 후보를 마구 대입해 볼 수 있나"]
    E -->|예| F["비밀번호 해싱 함수를 쓴다"]
    E -->|아니오| B

관련 항목

SHA-256 이 속하는 상위 분류

해시 함수 · 암호학적 해시 함수 · 해시 · 해시값 · 다이제스트 · SHA-2

SHA-256 을 대신할 수 있는 다른 해시 함수

SHA-1 · MD5 · SHA-3 · BLAKE2 · SHA-512 · xxHash

SHA-256 이 지키는 성질

충돌 저항성 · 역상 저항성 · 제2 역상 저항성 · 눈사태 효과 · 생일 문제

SHA-256 을 이루는 계산 얼개

머클-담고르 구조 · 압축 함수 · 패딩 · 비트 연산 · 초기 해시 값

SHA-256 을 노리는 공격

길이 확장 공격 · 충돌 공격 · 생일 공격 · 무차별 대입 공격 · 레인보우 테이블

SHA-256 을 무결성과 이름표에 쓰는 도구

Git · 머클 트리 · 체크섬 · 콘텐츠 주소 저장소 · 컨테이너 이미지 · 무결성

SHA-256 을 안에 품는 보안 규격

HMAC · 전자 서명 · 인증서 · TLS · JWT · 인증 기관

비밀번호 저장에 대신 쓰는 함수

비밀번호 해싱 · 솔트 · bcrypt · Argon2 · PBKDF2 · scrypt

다른 이름: SHA256 · Secure Hash Algorithm 256