빌드노트
RSA-OAEP에서 한글 64자가 too large로 실패하는 이유
2048비트 RSA와 SHA-256 OAEP의 평문 한도는 190바이트다. UTF-8로 바꾼 가 64개가 192바이트로 실패하는 이유와 최종 Buffer 길이 확인법을 설명한다. 189·190바이트 성공과 191·192바이트 거절을 대조하고, 큰 본문은 하이브리드 설계 검토 대상으로 구분한다.
Node의 publicEncrypt()에 짧은 문자열을 넘겼는데 data too large for key size로 실패한다면, 암호화 직전의 바이트 수를 확인한다. 2048비트 RSA 키와 SHA-256 OAEP 조합에서 허용되는 평문은 최대 190바이트다. ASCII A 64개는 64바이트지만, UTF-8로 바꾼 가 64개는 192바이트여서 이 한도를 넘는다. 화면에서 보이는 글자 수나 JavaScript 문자열의 length로 검사하면 이 차이를 놓친다.
2048비트 키에 평문 256바이트를 넣을 수는 없다
RFC 8017의 RSAES-OAEP 암호화 정의 (새 탭에서 열림)는 평문의 최대 길이를 k - 2 × hLen - 2바이트로 정한다. k는 RSA modulus의 바이트 길이이고, hLen은 OAEP 해시의 출력 바이트 길이다. 입력이 이 값을 초과하면 암호화를 중단한다. 여기서 바이트는 RFC가 쓰는 octet, 즉 8비트 단위다.
2048비트 modulus라면 k = 256이고 SHA-256의 출력은 32바이트이므로, 한도는 256 - 2 × 32 - 2 = 190바이트다. 정확히 190바이트까지 허용되며 191바이트부터 초과다. 공개 키 PEM 파일의 크기나 암호문의 Base64 글자 수를 이 식에 넣는 것이 아니다.
먼저 실제 암호화 호출의 oaepHash를 확인한다. Node.js v22.23.2의 publicEncrypt 문서 (새 탭에서 열림)에 따르면 이 옵션은 OAEP와 MGF1에 쓸 해시이며, 생략하면 SHA-1이다. 아래 계산과 실험은 암·복호화 양쪽에 oaepHash: 'sha256'을 명시한 조건이다. 코드가 옵션을 생략했다면 190바이트라는 숫자부터 그대로 적용해서는 안 된다.
기존 코드에서는 publicKey를 암호화에 쓰는 RSA 공개 키의 KeyObject, text를 암호화할 최종 문자열로 두고 다음을 확인한다. publicKey가 PEM 문자열이라면 먼저 require('node:crypto').createPublicKey(publicKey)로 가져온 객체를 사용한다. 이 코드는 키 내용이나 평문 대신 modulus 비트 수와 입력 바이트 수, 계산한 한도를 출력한다.
const { createHash } = require('node:crypto');
const oaepHash = 'sha256';
const input = Buffer.from(text, 'utf8');
const modulusBits = publicKey.asymmetricKeyDetails.modulusLength;
const k = Math.ceil(modulusBits / 8);
const maxBytes = k - 2 * createHash(oaepHash).digest().length - 2;
console.log({ modulusBits, bytes: input.length, maxBytes });
위 조건에서 text가 '가'.repeat(64)라면 출력은 { modulusBits: 2048, bytes: 192, maxBytes: 190 }이다. bytes > maxBytes면 해당 호출에 넣을 수 없는 입력이다. 190바이트인 입력까지 거절하는 >= 조건과도 구분한다. 한도 이내인데 실패한다면 크기 초과로 단정하지 말고 실제 키·패딩·해시 설정과 오류를 확인한다.
Node의 KeyObject 문서 (새 탭에서 열림)는 modulusLength의 단위를 비트로 설명한다. 반면 위의 input.length는 이미 만들어진 Buffer의 바이트 수다. JSON을 암호화한다면 JSON 직렬화까지 마친 문자열을 변환하고, 길이를 확인한 그 Buffer를 publicEncrypt()에 전달한다. 중간 필드의 글자 수만 재면 따옴표나 다른 필드가 더해진 최종 입력의 길이와 달라질 수 있다.
190바이트 입력에 한 글자를 더하면
다음 코드는 정확히 190바이트인 입력을 한 번 암호화한다. Node.js 22에서 내용을 UTF-8 파일 oaep-once.cjs로 저장하고 그 폴더에서 node oaep-once.cjs를 실행한다. 외부 패키지는 필요 없다. 실행할 때마다 새 2048비트 합성 RSA 키 한 쌍을 메모리에서 만들며, 개인 키나 평문은 출력·저장하지 않는다.
const { constants, generateKeyPairSync, publicEncrypt } = require('node:crypto');
const { publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
const text = '가'.repeat(63) + 'A';
const input = Buffer.from(text, 'utf8');
const encrypted = publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256'
}, input);
console.log({ bytes: input.length, ciphertextBytes: encrypted.length });
2026년 10월 24일 KST에 Windows x64, Node.js v22.23.2, OpenSSL 3.5.7에서 실행했을 때 { bytes: 190, ciphertextBytes: 256 }이 출력됐다. label은 생략했다. 이 출력은 암호화가 성공했다는 뜻이며, 복호화까지 검사하는 코드는 아니다.
text만 '가'.repeat(63) + 'AB'로 바꾸고 다시 실행하면 191바이트가 되어 암호화 단계에서 실패한다. 이 환경에서는 ERR_OSSL_RSA_DATA_TOO_LARGE_FOR_KEY_SIZE와 error:0200006E:rsa routines::data too large for key size가 나타났다. 오류 코드와 문구는 이 Node·OpenSSL 환경의 관찰값이며 다른 구현에서도 같다고 보장하지 않는다.
같은 조건에서 'A'.repeat(64)는 64바이트, '가'.repeat(63)는 189바이트로 성공했다. 반면 '가'.repeat(64)는 length가 64여도 192바이트여서 거절됐다. 이 예제의 가는 UTF-8에서 3바이트다. JavaScript 문자열의 length는 UTF-16 코드 단위 수이므로, 이를 UTF-8 바이트 수로 대신 쓸 수 없다. Buffer.byteLength 문서 (새 탭에서 열림)처럼 Buffer.byteLength(text, 'utf8')로 재거나, 위 코드처럼 실제 Buffer를 만든 뒤 길이를 읽는다.
별도 비교 실행에서는 새 키에 같은 패딩·해시를 지정해 이 문자열들과 189·190·191바이트의 합성 Buffer를 확인했다. 189·190바이트 입력은 복호화 후 원래 바이트와 같았고, 191·192바이트 입력은 암호화 단계에서 거절됐다. 초과 입력에는 암호문이 반환되지 않아 복호화를 시도하지 않았다.
한도를 넘는 본문은 RSA 분할 대상으로 삼지 않는다
짧은 값만 받기로 한 입력이라면 최종 바이트 수가 한도를 넘을 때 명확하게 거절하고, 입력 계약도 바이트 기준으로 정한다. 반면 긴 JSON이나 문서 본문을 보내야 한다면 데이터를 190바이트씩 잘라 RSA 암호문을 이어 붙이는 방식으로 해결하지 않는다. 이 실험은 한 번의 OAEP 호출만 확인했으며, 여러 조각의 순서·누락·결합을 검증하는 메시지 형식을 제공하지 않는다.
그 경우 검토할 대상은 본문을 대칭키의 인증된 암호화로 보호하고, 그 키를 공개 키 방식으로 전달하는 하이브리드 암호화 프로토콜이다. 예를 들어 RFC 7516의 JWE 암호화 절차 (새 탭에서 열림)는 키 암호화 모드에서 내용 암호화 키(CEK)를 암호화하는 단계와, CEK로 본문을 암호화하고 인증 태그를 만드는 단계를 구분한다. 이는 문서에 정의된 설계 방향이며, 여기서 구현하거나 상호 운용을 검증한 결과는 아니다. 한도만 늘리려고 합의된 OAEP 해시나 패딩을 임의로 바꾸는 것도 이 글의 해결 방법이 아니다.
직접 실행한 범위는 새 2048비트 합성 RSA 키, SHA-256 OAEP, 빈 label과 위 입력의 크기 경계다. 다른 키 크기·해시·런타임, 실제 인증서·사용자 데이터·네트워크 전송은 검증하지 않았다. 하이브리드 구현이나 운영에 사용할 알고리즘·키 관리 정책의 안전성을 검증한 것도 아니다.