PKCE 생성기

RFC 7636 PKCE code_verifier와 S256 code_challenge를 생성하고, 가진 verifier의 형식과 challenge 불일치 원인을 확인하고, 인가 URL과 토큰 요청 cURL까지 만듭니다. 모든 계산은 브라우저 안에서.

  • 브라우저에서 처리
  • 데이터가 브라우저 밖으로 나가지 않습니다
  • 무료 · 회원가입 불필요
새로 생성하면 verifier, state, nonce가 모두 바뀝니다. 길이는 43~128의 정수입니다. 길이를 바꾸거나 Ctrl/⌘+Enter를 눌러도 새 값이 생성됩니다. 기본 43자는 난수 32바이트(256비트)를 인코딩합니다.
S256은 verifier 문자열을 SHA-256으로 해시한 뒤 패딩 없는 base64url로 인코딩합니다. plain은 verifier 원문을 사용합니다.
ASCII 영문자, 숫자, - . _ ~를 43~128자 입력하세요. 앞뒤 공백은 무시합니다. 수정하면 challenge를 다시 계산합니다. Ctrl/⌘+L은 입력과 결과를 비웁니다.
code_challenge 대조토큰 요청이 실패하는 원인 찾기 challenge를 붙여 넣어 짝을 확인합니다. 패딩, 표준 Base64, 16진수 표기, verifier 문자열 대신 디코딩한 바이트를 해시한 경우를 구분합니다.

인가 요청 URL사용자를 여기로 리디렉션 엔드포인트는 http:// 또는 https://로 시작해야 하며 프래그먼트를 포함할 수 없습니다. 파라미터는 URL 인코딩하고 기존의 같은 이름 값을 교체합니다. scope에 openid가 있을 때만 nonce를 넣습니다.

"새로 생성"을 누를 때마다 state와 nonce도 새 난수가 됩니다. 사용자가 redirect_uri로 돌아오면 state를 대조하세요. nonce는 scope에 openid가 있을 때만 보냅니다.

토큰 요청(cURL)code와 함께 code_verifier 전송 폼 인코딩 필드와 verifier를 담은 POST 명령을 미리 보여 줍니다. 요청을 보내지는 않습니다. code가 비어 있으면 AUTHORIZATION_CODE를 사용합니다.

여기에 challenge와 요청 미리보기가 표시됩니다.

난수, SHA-256, 각 URL은 이 탭 안에서 계산합니다. 전송하거나 저장하지 않으며, 페이지를 새로 고치면 verifier는 사라집니다.

자세한 가이드 읽기 PKCE 생성기: OAuth 인가 코드 흐름을 제대로 연결하는 실전 가이드
예시·자세한 설명·자주 묻는 질문 실제 출력이 있는 예시, 다른 도구와의 차이, 자주 묻는 질문.

계산식은 RFC 7636 4.2절의 code_challenge = BASE64URL(SHA256(ASCII(code_verifier)))이며, base64url에는 =를 붙이지 않습니다. 생성기는 n바이트 난수를 base64url로 바꾼 뒤 필요한 길이만큼 자릅니다. 43자는 정확히 32바이트(256비트)입니다. SHA-256 자체는 해시 생성기와 같은 함수이고, 여기서는 32바이트 다이제스트를 16진수가 아니라 base64url로 표기한다는 점만 다릅니다.

RFC 7636 부록 B로 확인하기

verifier 칸에 dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk를 붙여 넣으면 E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM가 나옵니다. 부록 B의 값과 같습니다. 직접 만든 구현이 이 값을 내지 못한다면 인코딩이나 해시 대상이 잘못된 것입니다.

같은 verifier를 표준 Base64 표기 dBjftJeZ4CVP+mB92K27uhbUJU1p1r/wW1gFWFOEjXk=로 넣으면, 도구는 계산을 멈추고 *13번째 문자 ”+“는 쓸 수 없습니다. verifier에는 A–Z, a–z, 0–9와 - . _ 만 쓸 수 있습니다(RFC 7636 §4.1).*라고 표시한 뒤 base64url로 고치는 방법을 덧붙입니다. 한글 같은 비 ASCII 문자, 공백과 줄바꿈에도 각각 안내가 있고, 43128자를 벗어나면 실제 글자 수를 알려 줍니다.

네이버·카카오 로그인에서 PKCE 쓰기

네이버: 네이버 로그인 개발가이드의 3.5절 “Open ID Connect로 네이버 로그인 연동하기”에 PKCE가 나옵니다. 기존 OAuth 2.0 API와 다른 경로인 https://nid.naver.com/oauth2/authorize에 scope=openid(필수), code_challenge, code_challenge_method를 보내고, https://nid.naver.com/oauth2/token에 “PKCE로 동작하는 경우” code_verifier를 추가합니다. 표에 적힌 code_challenge_method의 기본값은 S256으로, 매개변수가 없으면 plain으로 보는 RFC 7636 4.3절과 다릅니다. 다른 서버로 옮길 때를 생각해 S256을 항상 명시하는 편이 안전하며, 이 도구의 인가 URL도 그렇게 만듭니다.

카카오: OIDC 메타데이터(https://kauth.kakao.com/.well-known/openid-configuration)는 code_challenge_methods_supported: ["S256"]을 반환하지만, 카카오 로그인 REST API 문서의 “인가 코드 받기” 요청 매개변수 표(client_id, redirect_uri, response_type, scope, prompt, login_hint, service_terms, state, nonce)에는 code_challenge가 없습니다(2026-10-01 확인). PKCE에 기대기 전에 테스트 앱으로 실제 동작을 확인하세요. 카카오의 scope는 쉼표로 구분하는데, 이 도구는 openid,profile_nickname처럼 쉼표로 적어도 openid를 인식해 nonce를 붙입니다. state는 요청마다 고유해야 한다고 문서에 적혀 있으니, 도구가 만드는 무작위 값을 그대로 쓸 수 있습니다.

인가 URL이 의도대로 만들어졌는지는 URL 분석기로 매개변수를 하나씩 펼쳐 확인할 수 있습니다.

challenge가 맞지 않을 때

code_challenge 대조를 펼쳐 앱이 실제로 보낸 값을 붙여 넣습니다. 부록 B의 verifier 기준으로 다음과 같이 판정합니다.

붙여 넣은 code_challenge판정
E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM=끝의 =가 불필요
E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw+cM=표준 Base64로 인코딩함
13d31e961a1ad8ec2f16b10c4c982e0876a878ad6df144566ee1894acb70f9c316진수 다이제스트
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkverifier 그대로(plain)
38v3YOi6zQgk1xkqk6Y5dvSDoBHqZrTh3mmWHxxWvyk디코딩한 32바이트 난수를 해시함

마지막 경우는 직접 구현할 때 자주 생깁니다. 난수 바이트를 보관해 두었다가, 실제로 보낸 43자 문자열 대신 바이트를 해시하는 실수입니다. plain을 선택한 상태에서 S256 결과를 붙여 넣으면 code_challenge_method=S256을 보내라고 안내합니다. 표준 Base64와 base64url의 차이는 Base64 인코드 / 디코드에서 두 알파벳을 비교해 볼 수 있습니다.

코드로 같은 값 구하기

부록 B의 verifier에 대해 아래 세 가지는 모두 E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM를 돌려줍니다. 이 페이지의 테스트가 실제로 실행합니다.

// 브라우저와 Node.js 20 이상(Web Crypto)
const base64url = (bytes) =>
  btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

function createVerifier() {
  return base64url(crypto.getRandomValues(new Uint8Array(32))); // 43자
}

async function createChallenge(verifier) {
  const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
  return base64url(new Uint8Array(digest));
}
import base64
import hashlib
import secrets


def create_verifier() -> str:
    return secrets.token_urlsafe(32)  # 32바이트 난수 -> 43자


def create_challenge(verifier: str) -> str:
    digest = hashlib.sha256(verifier.encode("ascii")).digest()
    return base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
package main

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
)

func createVerifier() string {
	b := make([]byte, 32)
	rand.Read(b)
	return base64.RawURLEncoding.EncodeToString(b) // 43자
}

func createChallenge(verifier string) string {
	sum := sha256.Sum256([]byte(verifier))
	return base64.RawURLEncoding.EncodeToString(sum[:])
}

로그인 뒤 ID 토큰에 들어 있는 nonce는 JWT 디코더로 확인할 수 있습니다.

다른 온라인 PKCE 도구와의 차이

2026-10-01 데스크톱 Chromium에서, 생성·대조하는 동안 페이지가 보낸 fetch, XMLHttpRequest, sendBeacon을 기록해 확인했습니다.

  • ToolBada(OAuth PKCE 생성기)와 encode64 한국어판: 브라우저 안에서 생성하지만, 가지고 있는 verifier나 challenge를 대조하는 기능은 없습니다.
  • Elysia Tools: 생성과 검증 모두 모드, verifier, challenge를 JSON으로 api.elysiatools.com에 보내고 서버가 돌려준 결과를 보여 줍니다.
  • tonyxu-io.github.io/pkce-generator: 브라우저 안에서 동작하고 부록 B 값도 맞지만, 입력을 검증하지 않습니다. 42자, + / =가 섞인 값, 한글, 빈칸 모두 challenge가 나옵니다.

이 도구는 붙여 넣은 verifier를 RFC 문법으로 한 글자씩 검사해 문제 위치를 알려 주고, challenge가 맞지 않을 때 원인의 종류까지 보여 줍니다. verifier는 탭 밖으로 나가지 않습니다.

제한

  • 방식은 RFC 7636이 정의한 S256과 plain뿐입니다. 생성하는 verifier는 base64url의 64개 문자만 쓰므로, 똑같이 유효한 .와 ~는 나오지 않습니다.
  • 엔트로피는 이 페이지에서 생성한 verifier에만 표시합니다. 붙여 넣은 verifier는 형식만 판정할 수 있고 무작위성은 알 수 없습니다.
  • 인가 서버에 연결하지 않으므로 토큰 교환을 대신할 수 없습니다. cURL은 퍼블릭 클라이언트 기준이며, 컨피덴셜 클라이언트는 클라이언트 인증을 더해야 합니다.
  • 제공자별 규칙(네이버의 기본값 S256 등)은 검사하지 않습니다.
  • verifier는 인가 요청 한 번마다 새로 만드세요. 고정된 verifier는 보호 효과가 없고, RFC 9700 2.1.1절도 서버가 고정값을 찾아 거부하도록 권합니다.

FAQ

verifier가 서버로 전송되거나 저장되나요?

아니요. 난수는 crypto.getRandomValues로, SHA-256은 crypto.subtle로 이 탭 안에서 계산합니다. 도구는 네트워크 요청을 보내지 않고, verifier를 localStorage, sessionStorage, 쿠키, URL에 쓰지 않습니다. 이 페이지는 분석·광고 스크립트도 불러오지 않습니다. 새로 고치면 verifier는 사라집니다.

code_verifier 길이는 얼마가 적당한가요?

기본값 43자는 32바이트 난수를 base64url로 인코딩한 것으로, RFC 7636 4.1절과 7.1절이 권장하는 방법입니다(256비트). 128자까지 늘릴 수 있습니다. 직접 입력한 verifier는 A–Z, a–z, 0–9와 - . _ ~로 된 43~128자면 형식상 유효하지만, 강도는 만든 방법에 달려 있습니다.

S256과 plain 중 무엇을 써야 하나요?

S256입니다. RFC 7636 4.2절은 SHA-256을 계산할 수 있는 클라이언트에 S256을 요구하고, RFC 9700은 인가 요청에 verifier를 드러내지 않는 방식이 현재 S256뿐이라고 설명합니다. OAuth 2.1 초안은 plain을 금지했습니다. 네이버와 카카오의 OIDC 메타데이터도 S256만 지원한다고 밝힙니다.

토큰 요청이 invalid_grant로 실패합니다. 무엇을 확인해야 하나요?

앱이 실제로 보낸 code_challenge를 "code_challenge 대조"에 붙여 넣으세요. verifier와 짝이 맞는지, 아니면 표준 Base64, 16진수, 끝의 =, verifier 그대로, 디코딩한 난수 바이트의 해시 같은 흔한 실수 중 무엇인지 알려 줍니다. 일치한다면 code_challenge_method=S256을 보냈는지, verifier가 같은 로그인 시도의 것인지, code를 한 번만 썼는지 확인하세요.

client_secret이 있는 서버 앱에도 PKCE가 필요한가요?

RFC 9700(OAuth 2.0 보안 모범 사례, 2025년)은 퍼블릭 클라이언트에는 PKCE를 필수로, 컨피덴셜 클라이언트에는 권장으로 정하고 있습니다. 인가 코드 주입 공격도 막아 주기 때문입니다. 컨피덴셜 클라이언트는 토큰 엔드포인트에서 클라이언트 인증도 해야 합니다. 도구의 cURL은 퍼블릭 클라이언트 기준이므로 client_secret 등은 직접 추가하세요.