개발 이야기3분 읽기

이 문자열이 무엇인지 알아내는 법

로그에 찍힌 낯선 값의 정체를 접두사·길이·앞 네 글자로 좁힙니다. 실제로 돌려 만든 판별표입니다.

로그나 API 응답에 정체 모를 문자열이 있습니다. 무엇으로 디코딩해야 하는지부터 정해야 합니다.

대부분은 앞 몇 글자로 갈립니다.

접두사가 형식을 말한다

$2y$10$4ynIhrlKty…       bcrypt 해시
$apr1$45y19kAp$…         Apache APR1 (htpasswd)
$1$…                     md5crypt
{SHA}L55TUjtiq8F…        htpasswd 의 SHA-1, 솔트 없음
eyJhbGciOiJIUzI1…        JWT
-----BEGIN …             PEM (키·인증서)
data:image/png;base64,   data URI
xn--bj0bj06e             punycode 도메인
=E2=80=94                quoted-printable
%ED%95%9C                URL 인코딩된 UTF-8

$로 시작하고 $로 둘러싸인 필드가 있으면 대체로 비밀번호 해시입니다. 첫 필드가 방식을 가리킵니다.

eyJ{"의 base64입니다. JSON을 base64로 감싼 것이면 무엇이든 eyJ로 시작하고, JWT가 그중 가장 흔합니다.

base64로 감싸도 파일 종류가 드러난다

base64는 3바이트를 4자로 바꿉니다. 그래서 파일의 앞 3바이트가 base64 앞 4자를 정합니다. 매직 바이트가 고정된 형식이면 그 4자도 항상 같습니다.

형식     앞 3바이트   base64 앞 4자
PNG      89 50 4e     iVBO
JPEG     ff d8 ff     /9j/
GIF      47 49 46     R0lG
PDF      25 50 44     JVBE
ELF      7f 45 4c     f0VM
SQLite   53 51 4c     U1FM
gzip     1f 8b 08     H4sI
zip      50 4b 03     UEsD
Java class ca fe ba   yv66

iVBORw0K로 시작하는 문자열을 보면 PNG를 base64로 감싼 것입니다. H4sI면 gzip입니다.

어디까지 안정적인가

4자까지입니다. 그 뒤는 형식에 따라 달라집니다.

gzip 명령이 만든 것   1f8b 0808 77e6946a   →  H4sICHfmlGoA
Node zlib 이 만든 것   1f8b 0800 00000000   →  H4sIAAAAAAAA

gzip 헤더의 4~8바이트는 수정 시각입니다. gzip 명령은 시각을 넣고 Node는 0을 넣습니다. 그래서 5번째 글자부터 갈립니다.

zip도 50 4b 03 04 다음이 버전과 플래그라 UEsD 이후가 흔들립니다. 앞 3바이트가 완전히 고정인 PNG·JPEG·PDF·ELF만 4자가 항상 같습니다.

길이로 좁힌다

해시라면 길이가 알고리즘을 거의 특정합니다.

알고리즘      바이트   hex   base64   base64url
MD5            16     32      24        22
SHA-1          20     40      28        27
RIPEMD-160     20     40      28        27
SHA-256        32     64      44        43
SHA-512        64    128      88        86
BLAKE2b-512    64    128      88        86

hex 32자면 MD5, 40자면 SHA-1이나 RIPEMD-160, 64자면 SHA-256입니다.

같은 길이가 겹치는 자리가 있습니다 — SHA-1과 RIPEMD-160은 둘 다 160비트라 구분되지 않습니다. 문맥으로 갈라야 합니다. 비트코인 주소 유도 과정이면 RIPEMD-160, 그 밖에는 대체로 SHA-1입니다.

base64로 적힌 해시는 끝의 =가 단서입니다. SHA-256을 base64로 하면 44자에 = 하나가 붙고, base64url이면 패딩을 빼서 43자입니다.

식별자

1fa8580d-7265-400b-967f-918c11703522   UUID   36자, 하이픈 4개
01M1AT7NKYZM7C354H747FC0ZJ             ULID   26자, 하이픈 없음
V1StGXR8_Z5jdHi6B-myT                  nanoid 21자 기본

UUID는 세 번째 조각의 첫 글자가 버전입니다.

…-400b-…   v4  무작위
…-11ee-…   v1  시간 기반, 정렬 안 됨
…-7abc-…   v7  시간 기반, 정렬됨
…-5984-…   v5  이름 기반 (SHA-1)

ULID는 Crockford base32라 I·L·O·U가 없습니다. 이 네 글자가 보이면 ULID가 아닙니다. 그리고 앞 10자가 타임스탬프라 같은 시각에 만든 값끼리는 앞부분이 같습니다.

문자셋으로 좁힌다

0-9a-f 만           hex
A-Z2-7 과 =         base32
A-Za-z0-9+/ 와 =    base64 (표준)
A-Za-z0-9-_         base64url
1-9A-HJ-NP-Za-km-z  base58 (0·O·I·l 없음)

+/가 보이면 표준 base64입니다. -_가 섞여 있으면 base64url이고, URL이나 JWT에서 옵니다.

base58은 헷갈리는 글자 넷(0·O·I·l)을 뺀 것이라, 비트코인 주소나 IPFS 해시에서 봅니다.

확인 순서

  1. 앞 5자를 봅니다. $·{·-----·eyJ·data:가 있으면 거기서 끝납니다
  2. 전부 hex이고 길이가 32·40·64·128이면 해시입니다
  3. base64로 보이면 디코딩해서 앞 바이트를 봅니다. 매직 바이트가 나오면 파일입니다
  4. 디코딩 결과가 다시 텍스트면 한 겹 더 감싼 것입니다. JSON이 나오는 경우가 많습니다
  5. 하이픈 4개에 36자면 UUID이고, 세 번째 조각 첫 글자로 버전을 봅니다
  6. 어느 쪽도 아니면 애플리케이션 고유 형식일 수 있습니다. 길이가 일정한지부터 봅니다

tools.onuel.dev에서 이 순서를 그대로 밟을 수 있습니다. Hex 덤프에 디코딩 결과를 넣으면 매직 바이트가 바로 보이고, Base64 디코딩은 텍스트로, 파일 Base64 변환은 파일로 되돌립니다. JWT로 보이면 JWT 디코더가 세 조각을 나눠 주고, 해시 길이를 대조할 때는 SHA-256MD5에 아무 값이나 넣어 자릿수를 비교하면 됩니다.

  • #디버깅
  • #인코딩
  • #해시