대칭 암호화는 암호화와 복호화에 같은 키를 사용합니다. 이 페이지에서 가장 중요한 개념은 일반 암호와 AEAD(연관 데이터가 있는 인증된 암호화) 암호의 차이입니다. AES-CBC나 AES-CTR만으로는 기밀성만 제공할 뿐 그 이상은 아닙니다: 평문을 읽을 수 없는 공격자라도 암호문의 비트를 뒤집을 수 있으며, 수신자는 무언가 잘못되었다는 표시 없이 변조된 결과를 복호화하게 됩니다. AES-GCM과 ChaCha20-Poly1305는 암호화와 함께 인증도 수행하므로, 변조가 감지되고 복호화가 완전히 실패합니다. 특별한 이유로 일반 모드를 강제하지 않는 한 AEAD 암호를 선호하십시오.
모든 함수는 sgcCrypto_AES, sgcCrypto_Modes, sgcCrypto_ChaCha, sgcCrypto_Poly1305에 들어 있습니다. 같은 키로 IV나 논스를 재사용하지 마십시오: CTR과 AEAD 암호의 경우, 재사용은 기밀성을 약화시키는 데 그치지 않으며, 특히 GCM에서는 인증 보장 자체가 완전히 깨집니다. 메시지마다 sgcRandomBytes로 새로 생성하십시오.
AES-GCM (권장)
대부분의 프로토콜이 기본으로 선택하는 AEAD 모드입니다. 논스는 12바이트이며, 메시지마다 무작위로 생성하십시오. aAAD는 인증되지만 암호화되지는 않으며, 이를 통해 암호문을 컨텍스트(예를 들어 메시지 ID나 프로토콜 버전)에 결속시키면서도, 암호문을 볼 수 있는 누구에게나 그 컨텍스트를 숨기지 않습니다.
function sgcAES_GCM_Encrypt(const aKey, aIV, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcAES_GCM_Decrypt(const aKey, aIV, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;
var
oKey, oIV, oCipher, oTag, oPlain, oAAD: TBytes;
begin
oIV := sgcRandomBytes(12);
oCipher := sgcAES_GCM_Encrypt(oKey, oIV, oPlain, oAAD, oTag);
{ send oIV, oCipher and oTag to the recipient }
if not sgcAES_GCM_Decrypt(oKey, oIV, oCipher, oAAD, oTag, oPlain) then
{ the ciphertext, the tag or the AAD was altered: reject it };
end;
ChaCha20-Poly1305, XChaCha20-Poly1305
RFC 8439의 AEAD 쌍입니다. AES-GCM이 AES 하드웨어 가속에 의존하는 반면, ChaCha20-Poly1305는 순수 소프트웨어에서 일정한 속도로 동작합니다. 이것이 TLS와 WireGuard가 AES-NI가 없는 모바일 및 임베디드 기기에서 이를 기본으로 사용하는 이유입니다. 논스는 12바이트로 GCM과 같은 제약이 있습니다. XChaCha20-Poly1305는 논스를 24바이트로 확장하여, 카운터를 추적하지 않고도 키의 수명 동안 무작위로 골라도 충분할 만큼 크며, 이는 서로 조율할 수 없는 여러 독립적인 작성자가 하나의 키를 공유할 때 중요합니다.
function sgcChaCha20Poly1305_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcChaCha20Poly1305_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;
function sgcXChaCha20Poly1305_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcXChaCha20Poly1305_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;
AES-CCM
CBC-MAC을 사용하는 카운터 모드로, RFC 3610과 NIST SP 800-38C입니다. 이는 제약된 환경과 IoT 스택이 선호하는 AEAD인데, 블록 암호 자체 외에는 아무것도, 즉 GHASH 테이블조차 필요로 하지 않기 때문입니다: Zigbee, 802.15.4, Bluetooth, TLS CCM 스위트가 모두 이를 지정합니다. 여유가 있는 곳에서는 GCM이 여전히 더 나은 선택인데, CCM은 데이터를 두 번 훑어야 하며 파이프라인으로 처리할 수 없기 때문입니다.
논스는 7바이트에서 13바이트이며 그 길이는 자유롭게 고를 수 있는 것이 아닙니다: 15에서 논스 길이를 뺀 값이 길이 필드의 크기이므로, 이것이 최대 메시지 크기를 결정하며, 13바이트 논스는 평문을 65535바이트로 제한합니다. 논스는 같은 키로 절대 반복되어서는 안 됩니다, GCM과 정확히 마찬가지입니다. aTagLen은 4, 6, 8, 10, 12, 14 또는 16바이트입니다.
function sgcAES_CCM_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; aTagLen: Integer; out aTag: TBytes): TBytes;
function sgcAES_CCM_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;
var
oKey, oNonce, oCipher, oTag, oPlain, oAAD: TBytes;
begin
oNonce := sgcRandomBytes(12);
oCipher := sgcAES_CCM_Encrypt(oKey, oNonce, oPlain, oAAD, 16, oTag);
{ send oNonce, oCipher and oTag to the recipient }
if not sgcAES_CCM_Decrypt(oKey, oNonce, oCipher, oAAD, oTag, oPlain) then
{ the ciphertext, the tag or the AAD was altered: reject it };
end;
AES-GMAC
GMAC은 정확히 평문을 비워두고 전체 메시지를 추가 데이터로 전달하며 실행하는 AES-GCM이며, 이는 표준이 이를 정의하는 방식입니다. 따라서 GCM의 논스 규칙을 하나도 빠짐없이 물려받습니다: aIV는 보통 12바이트이며 같은 키로 절대 반복되어서는 안 됩니다. GMAC에서 IV를 재사용하면 단순히 약화되는 데 그치지 않고, 해시 서브키가 유출되어 공격자가 태그를 그대로 위조할 수 있게 되므로, 그 실패는 점진적이 아니라 완전한 실패입니다. 해싱 및 메시지 인증 페이지의 AES-CMAC은 IV가 전혀 필요 없으며, 프로토콜에 이미 GCM이 들어 있는 경우가 아니라면 더 안전한 선택입니다. 태그는 16바이트입니다.
function sgcAES_GMAC(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_GMAC_Verify(const aKey, aIV, aData, aTag: TBytes): Boolean;
AES 블록 모드
이들은 인증 없이 기밀성만 제공합니다: 평문에 무결성이 필요하다면 해싱 및 메시지 인증의 MAC과 짝을 이루거나, 위의 AES-GCM을 대신 사용하십시오.
function sgcAES_CBC_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CBC_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;
function sgcAES_CTR(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_CFB_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CFB_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;
function sgcAES_OFB(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_ECB_Encrypt(const aKey, aPlain: TBytes): TBytes;
function sgcAES_ECB_Decrypt(const aKey, aCipher: TBytes): TBytes;
sgcAES_CTR와 sgcAES_OFB는 스스로가 스스로의 역함수입니다: 같은 함수를 같은 키와 IV로 다시 호출하면 복호화됩니다. ECB는 오직 이를 명시하는 오래된 형식과의 상호 운용을 위해서만 포함되어 있습니다: 동일한 평문 블록은 동일한 암호문 블록을 만들어내며, 이는 데이터의 구조를 노출시키므로 새로운 용도로는 선택해서는 안 됩니다.
CBC는 패딩이 전혀 없는 형태로도 사용할 수 있으며, 이는 Java가 AES/CBC/NoPadding으로 표기하는 모드입니다. 입력 길이는 16의 정확한 배수여야 하며, 그렇지 않으면 이 호출이 조용히 패딩을 채워 상대방이 읽을 수 없는 암호문을 만들어내는 대신 예외를 일으킵니다. 이는 프로토콜이 이미 블록에 정렬된 데이터를 보장하는 경우에만 안전합니다.
function sgcAES_CBC_Encrypt_NoPad(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CBC_Decrypt_NoPad(const aKey, aIV, aCipher: TBytes): TBytes;
암호문 스틸링은 블록 크기의 배수가 아닌 데이터에 대해 CBC의 패딩 오버헤드를 피합니다:
function sgcAES_CTS_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CTS_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;
패딩 방식
블록 모드는 평문이 16바이트의 배수이기를 요구하며, 패딩 방식은 이를 이루는 방법입니다. 새로운 용도로 옳은 선택은 PKCS#7뿐이며, 나머지 여섯 가지는 이미 다른 방식을 선택한 시스템의 데이터를 읽고 쓰기 위해 존재합니다. padPKCS5가 목록에 없는 것은 16바이트 블록에서는 이것이 곧 PKCS#7이기 때문이며, 두 이름은 정의될 당시의 블록 크기만 다를 뿐입니다.
type
TsgcPadding = (padNone, padPKCS7, padZero, padANSIX923, padISO7816_4,
padISO10126_2, padTBC);
function sgcPad_Add(const aData: TBytes; aBlockSize: Integer; aPadding: TsgcPadding): TBytes;
function sgcPad_Remove(const aData: TBytes; aBlockSize: Integer; aPadding: TsgcPadding; out aPlain: TBytes): Boolean;
function sgcAES_CBC_EncryptPad(const aKey, aIV, aPlain: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_CBC_DecryptPad(const aKey, aIV, aCipher: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_ECB_EncryptPad(const aKey, aPlain: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_ECB_DecryptPad(const aKey, aCipher: TBytes; aPadding: TsgcPadding): TBytes;
var
oKey, oIV, oPlain, oCipher: TBytes;
begin
oCipher := sgcAES_CBC_EncryptPad(oKey, oIV, oPlain, padANSIX923);
end;
위쪽에 있는 sgcAES_CBC_Encrypt와 sgcAES_ECB_Encrypt는 이들에 대한 PKCS#7 축약형입니다. sgcPad_Remove는 잘못된 패딩에 대해 예외를 일으키는 대신 False를 반환하며, 모든 종류의 실패에 대해 동일한 경로를 거칩니다. 이들을 구분하는 것이야말로 패딩 오라클이 필요로 하는 정보이기 때문입니다. padZero는 특히 주의하십시오: 패딩 바이트와 평문 끝에 있는 0을 구분할 수 없으므로, 끝에 있는 0을 모두 제거하며, 정당하게 0으로 끝날 수 있는 데이터는 잘려서 돌아옵니다.
AES 하드웨어 가속(AES-NI)
Delphi로 빌드한 Windows Win32 및 Win64 빌드에서 sgcCrypto_AES는 실행 시점에 CPU를 검사하고, CPU가 AES-NI, PCLMULQDQ, SSSE3를 지원한다고 보고하면 단일 블록 암호화, AES-CTR, AES-GCM(카운터 모드와 GHASH), AES-CBC 암호화를 설계상 상수 시간인 이 명령어 위에서 실행합니다. 이 명령어가 없을 때, 그리고 Linux64, macOS, iOS, Android에서는 이전과 같이 상수 시간 소프트웨어 코어를 사용합니다. CBC 복호화는 항상 소프트웨어 코어를 사용합니다. 네이티브 TLS 엔진은 자동으로 이 이점을 얻습니다.
sgcAES_HardwareAvailable은 이 빌드에 하드웨어 경로가 포함되어 있고 CPU가 이를 지원할 때 True를 반환합니다. 전역 변수 sgcAES_UseHardware는 기본값이 True이며, False로 설정하면 예를 들어 둘을 비교하기 위해 소프트웨어 코어를 강제로 사용합니다. 시작할 때 한 번만 설정하십시오.
var sgcAES_UseHardware: Boolean = True;
function sgcAES_HardwareAvailable: Boolean;
if sgcAES_HardwareAvailable then
Memo1.Lines.Add('AES-NI in use');
AES Key Wrap
키 래핑은 메시지가 아니라 키를 보호합니다: IV가 필요 없고, 결정적이며, 래핑된 키를 인증합니다. 이는 마스터 키 아래에 데이터 암호화 키를 저장하는 표준적인 방법으로, 예를 들어 HSM이나 KMS 마스터 키가 파일별 키를 래핑하는 경우입니다. RFC 3394는 래핑할 키가 8바이트의 배수여야 한다고 요구하며, RFC 5649는 임의 길이의 키를 위한 패딩을 추가합니다.
function sgcAES_KeyWrap(const aKEK, aPlainKey: TBytes): TBytes;
function sgcAES_KeyUnwrap(const aKEK, aWrapped: TBytes; out aPlainKey: TBytes): Boolean;
function sgcAES_KeyWrapPad(const aKEK, aPlainKey: TBytes): TBytes;
function sgcAES_KeyUnwrapPad(const aKEK, aWrapped: TBytes; out aPlainKey: TBytes): Boolean;
ChaCha20, Salsa20 (원시 스트림 암호)
위의 AEAD 구성 뒤에 있는 스트림 암호로, 원시 암호를 명시하는 프로토콜을 위해 직접 제공됩니다. 둘 다 인증하지 않습니다: 거의 항상 이 페이지 위쪽에 있는 Poly1305 AEAD 버전이 대신 필요한 것입니다. Salsa20은 ChaCha20의 전신으로, NaCl 시대 형식과의 호환성을 위해 남아 있습니다. 새로운 용도로는 ChaCha20을 선택하십시오.
function sgcChaCha20(const aKey, aNonce, aData: TBytes; aCounter: Cardinal): TBytes;
function sgcXChaCha20(const aKey, aNonce, aData: TBytes; aCounter: Cardinal): TBytes;
function sgcSalsa20(const aKey, aNonce, aData: TBytes; aCounter: Int64): TBytes;
function sgcXSalsa20(const aKey, aNonce, aData: TBytes; aCounter: Int64): TBytes;
레거시: DES와 NTLM
sgcCrypto_Legacy는 ECB 모드의 단일 DES와 DES 기반 NTLM v1 키 유도도 제공합니다. DES는 유효 키 길이가 56비트로, 현대 하드웨어로 몇 시간 안에 무차별 대입이 가능합니다. 이는 오직 이를 이름으로 명시하는 프로토콜(대개 NTLM 인증)과의 상호 운용을 위해서만 존재합니다. 새로운 용도로는 선택하지 말고, 대신 AES를 사용하십시오.
function sgcDES_EncryptECB(const aKey, aData: TBytes): TBytes;
function sgcDES_DecryptECB(const aKey, aData: TBytes): TBytes;
function sgcDES_NTLM7to8(const aKey7: TBytes): TBytes;