안전하고 깔끔한 32자 SECRET 생성기 만들기 (openssl rand 대안)

웹 애플리케이션을 개발하다 보면 .env 환경 변수 파일에 세션 암호화, JWT 서명 또는 OAuth 설정 등을 위한 32자 이상의 SECRET 키 를 등록해야 하는 일이 자주 발생합니다.

이때 흔히 사용하는 명령어 중 하나가 바로 openssl rand 입니다.

openssl rand -base64 32

하지만 이 명령어를 사용하면 몇 가지 불편한 점이 생깁니다.

  1. 길이 문제: 32바이트의 난수 데이터를 Base64로 인코딩하므로 최종 출력의 길이가 32자가 아닌 44자 가 됩니다.
  2. 특수문자 문제: 마지막에 패딩 문자인 =가 계속 붙고, 중간에 /+ 같은 특수문자가 섞여 나옵니다. 이 문자들은 간혹 환경 변수 파싱 오류를 일으키거나 소스 코드 내에서 문자열 처리를 할 때 골칫거리가 되곤 합니다.

이 글에서는 특수문자 없이 깔끔하면서도 대소문자와 숫자를 모두 사용하여 보안성을 극대화한 32자 시크릿 생성기 를 구현하는 여정을 소개합니다.


1. 16진수(Hex) 방식의 한계

가장 쉬운 대안은 16진수(Hex) 인코딩을 사용하는 것입니다. 16바이트의 난수를 16진수로 출력하면 정확히 32자가 됩니다.

openssl rand -hex 16
# 출력 예시: a4f8b2c6d7e0f1a2b3c4d5e6f7a8b9c0

이 방식은 특수문자 없이 0-9와 소문자 a-f로만 구성되어 안전하고 편리합니다.

하지만 사용 가능한 문자의 종류가 16개 로 제한되다 보니, 대소문자와 숫자를 모두 섞어 쓸 때보다 조합의 수(엔트로피)가 떨어지는 단점이 있습니다.

  • Hex (16진수) 32자: $16^{32} = 2^{128}$가지 조합 (128비트 보안성)
  • 알파벳 대소문자 + 숫자 (62진수) 32자: $62^{32} \approx 2^{190}$가지 조합 (190비트 보안성)

더 높은 보안 강도를 유지하면서 특수문자만 제거한 깔끔한 시크릿을 얻기 위해서는 대소문자와 숫자가 섞인 62개의 문자 풀(Pool) 을 활용해야 합니다.


2. 62개 문자 조합으로 시크릿 생성하기

macOS 및 리눅스에 기본 탑재된 암호학적 난수 생성 장치 /dev/urandom과 문자 필터링 도구 tr을 조합하면 이를 쉽게 구현할 수 있습니다.

LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32
# 출력 예시: 8dKfG3sR9tY1wPqL2mNbVxZpQoWnEjHi
  • LC_ALL=C: tr 명령어가 바이트 단위로 오차 없이 원활하게 작동하도록 로캘(Locale)을 표준 C로 고정합니다.
  • tr -dc 'A-Za-z0-9': 입력 흐름에서 지정한 문자군(A-Z, a-z, 0-9)을 제외한 모든 문자를 삭제(delete)하고 보존(complement)합니다.
  • head -c 32: 최종적으로 나온 문자열에서 정확히 32바이트(자)만 잘라냅니다.

3. 쉘 스크립트로 자동화하기 (gensecret.sh)

매번 이 긴 명령어를 입력하거나 찾아서 복사하는 대신, 터미널에서 옵션에 따라 편리하게 사용할 수 있는 유틸리티 스크립트를 작성해 봅니다.

요구사항은 다음과 같습니다:

  1. 기본 동작: 생성한 시크릿 텍스트를 화면(stdout)에 바로 출력합니다.
  2. 길이 변경 가능: 인자로 숫자를 입력하면 해당 길이만큼 시크릿을 생성합니다. (기본값: 32)
  3. 클립보드 연동: -c 옵션을 붙여 실행하면 터미널 화면에는 값을 노출하지 않고, 조용히 클립보드에 복사합니다.

작성한 gensecret.sh 스크립트 코드입니다.

#!/usr/bin/env bash

# Exit immediately if a command exits with a non-zero status
set -e

# Default settings
COPY_TO_CLIPBOARD=false
LENGTH=32

# Parse arguments flexibly (order does not matter)
for arg in "$@"; do
    if [[ "$arg" == "-c" ]]; then
        COPY_TO_CLIPBOARD=true
    elif [[ "$arg" =~ ^[0-9]+$ ]]; then
        LENGTH="$arg"
    fi
done

# Generate a cryptographically secure random string using A-Z, a-z, and 0-9
SECRET=$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c "$LENGTH")

# Handle output based on options
if [ "$COPY_TO_CLIPBOARD" = true ]; then
    if command -v pbcopy >/dev/null 2>&1; then
        echo -n "$SECRET" | pbcopy
    else
        # Fallback to stdout with a warning if pbcopy is not available (e.g. Linux)
        echo "Warning: pbcopy not found. Printing to stdout instead." >&2
        echo "$SECRET"
    fi
else
    # Default behavior: print the secret directly to stdout
    echo "$SECRET"
fi

4. 사용 및 활용 팁

스크립트 작성이 완료되었다면 실행 권한을 부여합니다.

chmod +x gensecret.sh

다양한 실행 명령어 예시

# 1. 기본 32자 화면 출력
./gensecret.sh
# 출력: u8WqLmYzXwRvUtSrQpOnMlKjIhGfEdCb

# 2. 16자 화면 출력
./gensecret.sh 16
# 출력: aB3eR2yL1nV8tG5j

# 3. 화면 출력 없이 클립보드에 바로 복사 (-c 옵션)
./gensecret.sh -c
# (화면 출력은 없으며 바로 Cmd+V로 환경 변수 파일 등에 붙여넣기 가능)

# 4. 복합 사용 (64자리 생성 후 클립보드 복사)
./gensecret.sh -c 64
./gensecret.sh 64 -c

전역 명령어로 등록하기

현재 폴더가 아닌 곳에서도 언제든지 gensecret 명령어로 사용하고 싶다면, 아래 두 가지 방법 중 하나를 선택할 수 있습니다.

방법 A: /usr/local/bin에 복사 (가장 권장)

sudo cp gensecret.sh /usr/local/bin/gensecret

이제 터미널을 열고 어디서나 gensecret -c 48 같은 방식으로 바로 실행할 수 있습니다.

방법 B: .zshrc에 Alias 등록

alias gensecret="~/bin/gensecret.sh"

자신의 환경에 맞는 편리한 경로에 파일을 보관한 뒤 alias를 설정해 두면 손쉽게 호출 가능합니다.

이제 불안정하고 입력이 번거로운 특수문자가 섞인 암호 대신, 강력하고 깔끔한 무작위 문자열 생성 유틸리티를 활용해 소중한 시간을 절약해 보세요!