암호화 프레임워크 (Crypto API)
Linux Crypto Framework(Crypto API)를 커널 암호 연산의 공통 추상화 계층으로 심층 분석합니다. skcipher/aead/hash/rng API 분류와 사용 패턴, synchronous vs asynchronous 요청 경로, 비대칭 암호와 공개키 연산, AEAD 인증 암호화, 커널 난수 생성기, AF_ALG 사용자 공간 인터페이스를 다룹니다. 하드웨어 가속은 암호화 하드웨어 가속, 드라이버 구현은 암호화 드라이버 구현 가이드에서 별도로 다룹니다.
핵심 요약
- 전제 결합 — 보안, 성능, 아키텍처 지식을 함께 적용합니다.
- 경계 명확화 — API 경계와 ABI 영향 범위를 먼저 확인합니다.
- 위험 관리 — UAF, race, side-effect 가능성을 우선 점검합니다.
- 계측 기반 판단 — 추측 대신 데이터로 개선 여부를 판단합니다.
- 점진 적용 — 실험 범위를 작게 시작해 단계적으로 확장합니다.
단계별 이해
- 가설 수립
문제와 개선 목표를 수치로 정의합니다. - 제약 분석
호환성, 안정성, 보안 제약을 먼저 확인합니다. - 실험 적용
최소 변경으로 효과와 부작용을 측정합니다. - 정식 반영
검증된 변경만 문서화해 반영합니다.
Linux Crypto Framework 개요
Linux 커널의 Crypto Framework(Crypto API)는 암호화 알고리즘을 커널 내에서 사용할 수 있도록 하는 통합 프레임워크입니다. 소프트웨어 구현과 하드웨어 가속(AES-NI, ARM CE, QAT, CAAM, CPT 등)을 동일한 인터페이스로 제공하여, 알고리즘 사용자(IPsec, dm-crypt, kTLS 등)가 구현 방식을 신경 쓰지 않아도 됩니다.
어디에서 사용되는가
| 사용처 | 사용하는 API | 대표 알고리즘 | 데이터 규모 |
|---|---|---|---|
| IPsec ESP | aead (gcm, authenc) | rfc4106(gcm(aes)) | 패킷 단위 (~1500B) |
| dm-crypt (LUKS) | skcipher (xts, cbc) | xts(aes) | 섹터 단위 (512~4096B) |
| kTLS | aead (gcm) | gcm(aes) | 레코드 단위 (~16KB) |
| WireGuard | aead + kpp | rfc7539(chacha20,poly1305) | 패킷 단위 |
| fscrypt (ext4/f2fs) | skcipher (xts, adiantum) | xts(aes), adiantum | 블록 단위 (4096B) |
| 모듈/펌웨어 서명 | sig + shash | ecdsa + sha256 | 다이제스트 (32~64B) |
| IMA 무결성 | shash | sha256 | 파일 전체 |
| TCP SYN Cookie | shash (siphash) | siphash | 헤더 필드 (수십 B) |
| eCryptfs | skcipher + shash | cbc(aes) + sha256 | 파일 블록 |
Linux Crypto Framework 아키텍처 원리
Crypto Framework (Crypto API)는 알고리즘 구현과 사용을 분리하는 프레임워크 패턴으로 설계되어 있습니다. 핵심 개념은 세 가지입니다:
- Algorithm (알고리즘 등록): 각 암호화 구현체(SW 또는 HW)가
crypto_register_alg()으로 자신을 등록합니다. 같은 알고리즘의 여러 구현이 공존하며, priority 값으로 우선순위를 매깁니다. - Transform (tfm): 사용자가
crypto_alloc_skcipher("cbc(aes)", ...)를 호출하면, 커널이 priority가 가장 높은 구현을 선택하여 인스턴스(tfm)를 생성합니다. 키를 설정하면 이 tfm으로 반복 암호화가 가능합니다. - Request: 실제 데이터 처리 단위. scatterlist(물리 메모리(Physical Memory) 분산 목록)로 데이터를 전달하여 DMA 친화적인 zero-copy 처리가 가능합니다.
알고리즘 조합 (Template) 원리
Crypto Framework (Crypto API)의 강력한 특성 중 하나는 템플릿 기반 알고리즘 조합입니다. "cbc(aes)"에서 cbc는 운용 모드 템플릿이고 aes는 기본 블록 암호입니다. 커널은 이를 재귀적으로 해석하여:
알고리즘 이름 해석 예시: "authenc(hmac(sha256),cbc(aes))"는 다음과 같이 분해됩니다:
authenc— 인증+암호화 템플릿hmac— MAC 템플릿sha256— 해시(Hash) 알고리즘cbc— 블록 암호 모드 템플릿aes— 블록 암호 알고리즘
이 구조 덕분에 새 블록 암호(예: SM4)를 추가하면 기존 모든 템플릿(cbc, gcm, xts 등)과 자동 조합됩니다.
Fallback 메커니즘: H/W 가속기가 특정 키 크기나 입력 크기를 지원하지 못하면, 자동으로 priority가 낮은 S/W 구현으로 fallback됩니다. 이는 CRYPTO_ALG_NEED_FALLBACK 플래그와 crypto_alloc_*의 type/mask 매개변수로 제어됩니다.
Crypto API 소스 디렉토리 구조
Crypto Framework의 소스 코드는 커널 트리에서 명확하게 구역화되어 있습니다. 코드를 읽을 때 "어디서 무엇을 찾아야 하는지" 아는 것이 첫걸음입니다:
| 디렉토리/파일 | 역할 | 주요 파일 예시 |
|---|---|---|
crypto/ | 프레임워크 코어 + SW 구현 | api.c, algapi.c, skcipher.c, aead.c |
crypto/ (알고리즘) | 순수 C 알고리즘 구현 | aes_generic.c, sha256_generic.c, gcm.c |
crypto/ (템플릿) | 운용 모드 템플릿 | cbc.c, ctr.c, xts.c, authenc.c, hmac.c |
include/crypto/ | API 헤더 (사용자 인터페이스) | skcipher.h, aead.h, hash.h, akcipher.h |
include/linux/crypto.h | 코어 타입 정의 | crypto_alg, crypto_tfm, 플래그 상수 |
arch/x86/crypto/ | x86 ISA 가속 구현 | aesni-intel_glue.c, sha256_ssse3_glue.c |
arch/arm64/crypto/ | ARM64 CE 가속 구현 | aes-ce-glue.c, sha2-ce-glue.c |
drivers/crypto/ | HW 가속기 드라이버 | caam/, qat/, marvell/, ccp/ |
crypto/testmgr.c | 자가 테스트 프레임워크 | 알고리즘 등록 시 자동 테스트 벡터 검증 |
crypto/tcrypt.c | 벤치마크 모듈 | 알고리즘 속도·처리량 측정 |
crypto/af_alg.c | AF_ALG 소켓 인터페이스 | 사용자 공간 Crypto API 접근 |
crypto/crypto_engine.c | HW 드라이버 큐 관리 | 비동기 요청 직렬화·백로그 자동 처리 |
코드 읽기 순서 권장: (1) include/crypto/skcipher.h로 API 인터페이스 파악 → (2) crypto/skcipher.c로 tfm/request 관리 로직 확인 → (3) crypto/aes_generic.c로 SW 구현 참조 → (4) arch/x86/crypto/aesni-intel_glue.c로 HW 가속 glue 코드 분석 → (5) drivers/crypto/caam/로 실제 HW 드라이버 구조 이해. 이 순서로 읽으면 추상화 계층별로 자연스럽게 이해됩니다.
알고리즘 유형
Crypto API는 8가지 알고리즘 유형을 지원하며, 각각 전용 API와 전용 request 구조체를 가집니다. 목적에 맞는 유형을 선택하는 것이 첫 번째 설계 결정입니다:
| 유형 | API | 예시 | 동기/비동기 | 주요 사용처 | 상세 섹션 |
|---|---|---|---|---|---|
| 대칭 암호 (Cipher) | crypto_skcipher | AES-CBC, AES-XTS, ChaCha20 | 양쪽 | 디스크 암호화, VPN 터널 | 대칭 암호 예제 |
| AEAD | crypto_aead | AES-GCM, ChaCha20-Poly1305 | 양쪽 | IPsec ESP, kTLS, WireGuard | AEAD |
| 동기 해시 | crypto_shash | SHA-256, HMAC, CRC32C | 동기만 | 무결성 검증, 서명 전처리 | 해시 예제 |
| 비동기 해시 | crypto_ahash | SHA-256 (DMA 엔진) | 비동기 | HW 해시 가속기 | shash/ahash 구현 |
| 비대칭 암/복호화 | crypto_akcipher | RSA | 양쪽 | 키 포장, 인증서 암호화 | 비대칭 암호 |
| 전자서명 | crypto_sig | ECDSA, RSA | 동기 | 모듈 서명, 펌웨어 검증 | 서명 API |
| 키 합의 (KPP) | crypto_kpp | ECDH, DH, Curve25519 | 양쪽 | TLS/IPsec 핸드셰이크 | KPP 예제 |
| 난수 생성 (RNG) | crypto_rng | DRBG, Jitter RNG | 동기 | FIPS DRBG, Nonce 생성 | 난수 생성기 |
| 압축 | crypto_comp / acomp | LZ4, ZSTD, Deflate | 양쪽 | zswap, Btrfs, QAT 압축 | QAT 압축 |
유형 선택 가이드: 데이터 기밀성만 필요하면 skcipher, 기밀성+무결성이 동시에 필요하면 aead, 무결성 검증만 필요하면 shash/ahash를 사용하세요. 새 프로토콜 설계에서는 거의 항상 AEAD를 선택하는 것이 권장됩니다. skcipher+별도 MAC 조합은 Padding Oracle 등 조합 취약점의 위험이 있습니다.
비대칭 암호와 공개키 연산
커널 Crypto Framework에서 비대칭 연산은 하나의 API로 뭉뚱그리지 않고 암/복호화, 서명/검증, 키 합의를 각각 다른 인터페이스로 분리합니다. 이 구분을 이해하지 못하면 RSA, ECDSA, ECDH를 같은 계층의 대체재처럼 오해하게 됩니다.
| 연산 | 커널 API | 대표 알고리즘 | 커널에서 자주 쓰는 위치 |
|---|---|---|---|
| 공개키 암호화 / 개인키 복호화 | crypto_akcipher | RSA | 세션 키 포장, 키 블롭 보호, 인증서 기반 부트 체인 |
| 개인키 서명 / 공개키 검증 | crypto_sig | ECDSA, RSA | 모듈 서명, 펌웨어(Firmware) 검증, 무결성(Integrity) 검증 |
| 공유 비밀 계산 | crypto_kpp | ECDH, DH | TLS/IPsec 핸드셰이크, 세션 키 합의 |
skcipher 또는 aead로 처리하는 하이브리드 구성이 표준적입니다.
중요한 차이: crypto_akcipher는 공개키로 암호화하고 개인키로 복호화하는 경로를 다루며, crypto_sig는 서명과 검증을 위해 별도 API를 사용합니다. crypto_kpp는 암/복호화가 아니라 양측이 동일한 공유 비밀을 계산하는 용도이므로, 의미상 완전히 다른 계층입니다.
crypto_akcipher_set_pub_key(),
crypto_akcipher_set_priv_key(), crypto_sig_set_pubkey(),
crypto_sig_set_privkey()는 BER/DER 형태의 키와 알고리즘 파라미터를 기대합니다.
즉, 단순히 RSA modulus나 ECC 좌표만 raw 바이트로 넘기는 식의 코드는 바로 맞지 않을 수 있습니다.
RSA AKCIPHER 예제
공개키 암호화 API는 crypto_alloc_akcipher()로 tfm을 얻고, akcipher_request에
입출력(I/O) scatterlist를 연결한 뒤 crypto_akcipher_encrypt() 또는
crypto_akcipher_decrypt()를 호출하는 형태입니다. 공개키/개인키는 BER/DER로 인코딩된 형태가
필요하다는 점이 대칭 API와 가장 크게 다릅니다.
#include <crypto/akcipher.h>
#include <linux/scatterlist.h>
static int rsa_encrypt_keyblob(const void *pub_der, unsigned int pub_der_len,
const u8 *plain, unsigned int plain_len,
u8 *cipher, unsigned int *cipher_len)
{
struct crypto_akcipher *tfm;
struct akcipher_request *req;
struct scatterlist src, dst;
int ret;
tfm = crypto_alloc_akcipher("rsa", 0, 0);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
ret = crypto_akcipher_set_pub_key(tfm, pub_der, pub_der_len);
if (ret)
goto out_free_tfm;
req = akcipher_request_alloc(tfm, GFP_KERNEL);
if (!req) {
ret = -ENOMEM;
goto out_free_tfm;
}
sg_init_one(&src, plain, plain_len);
sg_init_one(&dst, cipher, *cipher_len);
akcipher_request_set_crypt(req, &src, &dst, plain_len, *cipher_len);
ret = crypto_akcipher_encrypt(req);
if (!ret)
*cipher_len = req->dst_len;
akcipher_request_free(req);
out_free_tfm:
crypto_free_akcipher(tfm);
return ret;
}
akcipher_request는
src, dst, src_len, dst_len를 갖는 비동기 요청 객체입니다.
소프트웨어 구현은 즉시 완료될 수 있지만, 하드웨어 가속기나 backlog 환경을 고려하면 completion 경로를 염두에 두는 편이 안전합니다.
전자서명 API 예제
서명 API는 request 객체를 쓰지 않고 tfm에 직접 crypto_sig_sign() 또는
crypto_sig_verify()를 호출합니다. 상위 계층이 이미 해시를 계산해 둔 상태에서 digest에 대해
서명/검증을 수행하는 식으로 사용하는 경우가 많습니다.
#include <crypto/sig.h>
static int verify_firmware_digest(const void *pub_der, unsigned int pub_der_len,
const u8 *sig, unsigned int sig_len,
const u8 *digest, unsigned int digest_len)
{
struct crypto_sig *tfm;
int ret;
tfm = crypto_alloc_sig("ecdsa", 0, 0);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
ret = crypto_sig_set_pubkey(tfm, pub_der, pub_der_len);
if (ret)
goto out_free_tfm;
ret = crypto_sig_verify(tfm, sig, sig_len, digest, digest_len);
out_free_tfm:
crypto_free_sig(tfm);
return ret;
}
서명 출력 버퍼는 crypto_sig_maxsize()로 산정하고, 검증에 넘기는 digest 길이는
상위 프로토콜이 정한 해시 길이와 정확히 맞춰야 합니다. 특히 ECDSA는 곡선 종류와 DER 인코딩 길이에 따라
서명 길이가 가변적일 수 있으므로, 고정 길이 배열을 가정하면 구현이 깨질 수 있습니다.
KPP ECDH 예제
crypto_kpp는 암호문을 만드는 API가 아니라 공유 비밀을 계산하는 API입니다.
ECDH에서는 개인키를 struct ecdh로 표현한 뒤 crypto_ecdh_encode_key()로
패킷(Packet) 형태로 포장해 crypto_kpp_set_secret()에 넘깁니다.
#include <crypto/kpp.h>
#include <crypto/ecdh.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
static int ecdh_shared_secret(const u8 *priv, unsigned int priv_len,
const u8 *peer_pub, unsigned int peer_pub_len,
u8 *secret, unsigned int *secret_len)
{
struct crypto_kpp *tfm;
struct kpp_request *req;
struct ecdh params = {
.key = (char *)priv,
.key_size = priv_len,
};
struct scatterlist src, dst;
char *packed = NULL;
unsigned int packed_len;
int ret;
tfm = crypto_alloc_kpp("ecdh", 0, 0);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
packed_len = crypto_ecdh_key_len(¶ms);
packed = kmalloc(packed_len, GFP_KERNEL);
if (!packed) {
ret = -ENOMEM;
goto out_free_tfm;
}
ret = crypto_ecdh_encode_key(packed, packed_len, ¶ms);
if (ret)
goto out_free_key;
ret = crypto_kpp_set_secret(tfm, packed, packed_len);
if (ret)
goto out_free_key;
req = kpp_request_alloc(tfm, GFP_KERNEL);
if (!req) {
ret = -ENOMEM;
goto out_free_key;
}
sg_init_one(&src, peer_pub, peer_pub_len);
sg_init_one(&dst, secret, *secret_len);
kpp_request_set_input(req, &src, peer_pub_len);
kpp_request_set_output(req, &dst, *secret_len);
ret = crypto_kpp_compute_shared_secret(req);
if (!ret)
*secret_len = req->dst_len;
kpp_request_free(req);
out_free_key:
kfree(packed);
out_free_tfm:
crypto_free_kpp(tfm);
return ret;
}
| 알고리즘 | 강점 | 제약 | 커널에서 보통 맡는 역할 |
|---|---|---|---|
| RSA | 폭넓은 호환성, AKCIPHER로 직관적 | 키와 서명 크기가 크고 연산 비용이 큼 | 키 포장, 인증서 기반 암/복호화, 일부 서명 검증(Signature Verification) |
| ECDSA | 짧은 키와 짧은 서명, 검증 비용 절감 | 곡선/인코딩 관리가 필요 | 모듈, 펌웨어, 이미지 무결성 검증 |
| ECDH | 세션 키 합의에 효율적 | 단독으로 기밀성이나 인증을 주지 않음 | TLS, IPsec, WireGuard 전단의 공유 비밀 생성 |
커널에서 지원하는 비대칭 알고리즘 전체 목록
| 알고리즘 | 커널 이름 | API 유형 | 키 크기 | 서명/출력 크기 | 용도 | HW 가속 |
|---|---|---|---|---|---|---|
| RSA | rsa | akcipher + sig | 2048~4096 비트 | 키 길이와 동일 | 키 포장, 서명 검증 | CAAM PKHA, CPT AE, QAT |
| ECDSA P-256 | ecdsa-nist-p256 | sig | 256 비트 | ~72B (DER) | 모듈 서명, 펌웨어 검증 | CAAM, CPT AE |
| ECDSA P-384 | ecdsa-nist-p384 | sig | 384 비트 | ~104B (DER) | 고보안 서명 | CAAM, CPT AE |
| ECDH P-256 | ecdh-nist-p256 | kpp | 256 비트 | 32B (공유 비밀) | TLS/IPsec 키 합의 | CAAM, CPT AE |
| ECDH P-384 | ecdh-nist-p384 | kpp | 384 비트 | 48B (공유 비밀) | 고보안 키 합의 | CAAM |
| Curve25519 | curve25519 | kpp | 256 비트 | 32B (공유 비밀) | WireGuard, 현대 프로토콜 | SW 전용 (ARM NEON 최적화) |
| DH | dh | kpp | 2048~8192 비트 | 그룹 크기와 동일 | 레거시 IPsec IKEv1 | CAAM, QAT |
| SM2 | sm2 | sig + akcipher | 256 비트 | ~72B (DER) | 중국 국가 표준 | 일부 HiSilicon |
| ECRDSA | ecrdsa | sig | 256/512 비트 | 64/128B | 러시아 GOST 표준 | SW 전용 |
TLS 핸드셰이크에서 비대칭 연산의 역할
비대칭 암호의 핵심 사용처는 대칭 세션 키를 안전하게 합의하는 것입니다. TLS 1.3 핸드셰이크를 예로 들면, ECDH(키 합의) → ECDSA(서명 검증) → HKDF(키 파생) → AES-GCM(대칭 암호화) 순서로 비대칭·대칭 연산이 결합됩니다:
커널 모듈 서명 검증 파이프라인
커널 모듈 서명(CONFIG_MODULE_SIG)은 비대칭 암호의 대표적 커널 내 사용처입니다. 빌드 시 개인키로 모듈을 서명하고, 로드 시 내장 공개키로 검증합니다:
키 형식과 DER 인코딩
커널 Crypto API의 비대칭 연산은 키를 DER(Distinguished Encoding Rules) 형식으로 요구합니다. PEM(Base64) 형식이 아닌 바이너리 ASN.1/DER 형태이며, 알고리즘에 따라 구조가 다릅니다:
# ━━━ 키 형식 변환과 커널 호환성 ━━━
# RSA 공개키: PEM → DER 변환
openssl rsa -in rsa_priv.pem -pubout -outform DER -out rsa_pub.der
# → SubjectPublicKeyInfo(SPKI) 형태, crypto_akcipher_set_pub_key()에 직접 전달
# RSA 개인키: PEM → DER 변환 (PKCS#8)
openssl pkcs8 -in rsa_priv.pem -topk8 -nocrypt -outform DER -out rsa_priv.der
# → PKCS#8 형태, crypto_akcipher_set_priv_key()에 전달
# ECDSA P-256 키쌍 생성
openssl ecparam -name prime256v1 -genkey -noout -outform DER -out ec_priv.der
openssl ec -in ec_priv.der -inform DER -pubout -outform DER -out ec_pub.der
# → crypto_sig_set_pubkey()에 DER 형태 직접 전달
# 커널 모듈 서명용 키 생성 (커널 빌드 자동)
# certs/signing_key.pem — 빌드 시 자동 생성
# → .builtin_trusted_keys 키링에 X.509 인증서로 내장
# DER 키 구조 확인
openssl asn1parse -in rsa_pub.der -inform DER
# 0:d=0 hl=4 l=290 cons: SEQUENCE
# 4:d=1 hl=2 l= 13 cons: SEQUENCE ← AlgorithmIdentifier
# 19:d=1 hl=4 l=271 prim: BIT STRING ← 공개키 데이터
비대칭 연산 HW 가속
비대칭 연산은 대칭 연산에 비해 수백~수천 배 느리기 때문에, TLS 핸드셰이크가 많은 환경에서 HW 가속의 효과가 큽니다. 커널의 주요 HW 가속기별 지원 현황입니다:
| 가속기 | RSA Sign | RSA Verify | ECDSA Sign | ECDSA Verify | ECDH | DH | 드라이버 |
|---|---|---|---|---|---|---|---|
| Intel QAT 4xxx | ~100K 2048 | ~1M 2048 | - | - | - | ~100K | qat_4xxx |
| AMD CCP | ~10K 2048 | ~100K 2048 | ~8K P-256 | ~15K P-256 | ~15K | ~10K | ccp |
| NXP CAAM (PKHA) | ~2K 2048 | ~20K 2048 | ~4K P-256 | ~8K P-256 | ~8K | ~2K | caampkc |
| Marvell CPT AE | ~50K 2048 | ~200K 2048 | ~80K P-256 | ~100K P-256 | ~80K | ~50K | otx2_cpt |
| ARM CryptoCell | ~1K 2048 | ~5K 2048 | ~2K P-256 | ~4K P-256 | ~4K | - | ccree |
| SW (커널 RSA/ECC) | ~500 2048 | ~10K 2048 | ~2K P-256 | ~5K P-256 | ~5K | ~500 | rsa-generic |
RSA Sign vs Verify 성능 차이: RSA 서명(개인키 연산)은 CRT(Chinese Remainder Theorem) 최적화를 사용해도 검증(공개키 연산)의 10~50배 느립니다. 공개키 지수 e=65537이 작아 검증은 빠르지만, 개인키 지수 d는 크기 때문입니다. 이것이 서버 측 SSL 핸드셰이크에서 HW 가속이 특히 중요한 이유입니다.
ECDSA vs RSA 선택 기준: 동일 보안 수준(128비트)에서 ECDSA P-256(32B 키)은 RSA-3072(384B 키)보다 키·서명 크기가 10배 작고 검증도 빠릅니다. 새 시스템에서는 ECDSA를 권장하며, 커널 모듈 서명도 6.x부터 ECDSA를 기본으로 지원합니다.
Curve25519 vs NIST P-256: Curve25519는 상수 시간 구현이 간단하여 부채널 공격에 강하고, 성능도 우수합니다. WireGuard가 Curve25519를 선택한 이유입니다. 다만 FIPS 인증이 필요한 환경에서는 NIST P-256을 사용해야 합니다.
비대칭 연산 성능 특성
| 연산 | SW (단일 코어) | CAAM PKHA | CPT AE | QAT | 비고 |
|---|---|---|---|---|---|
| RSA-2048 Sign | ~1 ms | ~0.5 ms | ~0.02 ms | ~0.01 ms | CRT 최적화 포함 |
| RSA-2048 Verify | ~0.05 ms | ~0.05 ms | ~0.005 ms | ~0.001 ms | e=65537, 빠름 |
| RSA-4096 Sign | ~8 ms | ~4 ms | ~0.1 ms | ~0.05 ms | 키 길이 2× → 연산 8× |
| ECDSA P-256 Sign | ~0.3 ms | ~0.25 ms | ~0.012 ms | - | RSA-3072 동등 보안 |
| ECDSA P-256 Verify | ~0.5 ms | ~0.12 ms | ~0.01 ms | - | 서명보다 느림 (RSA와 반대) |
| ECDH P-256 | ~0.3 ms | ~0.12 ms | ~0.012 ms | - | TLS 핸드셰이크 병목 |
| X25519 (Curve25519) | ~0.05 ms | - | - | - | SW만, ARM NEON 최적화 |
관련 섹션 연결: AKCIPHER/KPP 드라이버 구현 상세는 AKCIPHER 드라이버 구현과 KPP 드라이버 구현, 펌웨어 검증 파이프라인 코드는 펌웨어 검증 파이프라인, FIPS 요구사항과 최소 키 길이는 FIPS 모드와 자가 테스트, 하드웨어 오프로드 선택 기준은 암호화 오프로드 결정 가이드를 함께 참고하세요.
해시 사용 예제
해시(Hash)는 임의 길이 데이터를 고정 길이 다이제스트(Digest)로 축약하는 일방향 함수입니다. Crypto API는 shash(동기 해시)와 ahash(비동기 해시) 두 인터페이스를 제공합니다. 대부분의 경우 shash가 간결하고 충분하며, HW DMA 해시 엔진을 사용할 때만 ahash가 필요합니다:
| 인터페이스 | API | 동기/비동기 | 입력 형태 | 적합 상황 |
|---|---|---|---|---|
| shash | crypto_shash | 동기 | 가상 주소 (vaddr) | SW 해시, CPU 명령어 가속 (SHA-NI) |
| ahash | crypto_ahash | 비동기 | scatterlist (DMA 가능) | HW DMA 해시 엔진, 대용량 분산 버퍼 |
shash vs ahash 선택: 커널 내부적으로 shash를 ahash로 자동 래핑하므로, ahash 인터페이스에서도 shash 구현이 사용될 수 있습니다. SW 전용이면 shash, HW DMA 엔진이면 ahash를 선택하세요. 예제 코드는 개념 설명용이며, 실제 커널 트리 구현(crypto/sha256_generic.c 등)을 함께 참고하세요.
#include <crypto/hash.h>
#include <linux/slab.h>
static int calc_sha256(const u8 *data, unsigned int len, u8 *digest)
{
struct crypto_shash *tfm;
struct shash_desc *desc;
int ret;
tfm = crypto_alloc_shash("sha256", 0, 0);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
desc = kmalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL);
if (!desc) {
crypto_free_shash(tfm);
return -ENOMEM;
}
desc->tfm = tfm;
ret = crypto_shash_digest(desc, data, len, digest);
kfree(desc);
crypto_free_shash(tfm);
return ret;
}
/* 스택 할당 대안: SHASH_DESC_ON_STACK 매크로 사용
* 힙 할당 오버헤드 없이 스택에 shash_desc를 직접 배치합니다.
* 단, descsize가 크면 스택 사용량 초과 위험이 있으므로
* CONFIG_FRAME_WARN을 초과하지 않는 작은 알고리즘에 적합합니다. */
static int calc_sha256_stack(const u8 *data, unsigned int len, u8 *digest)
{
struct crypto_shash *tfm = crypto_alloc_shash("sha256", 0, 0);
SHASH_DESC_ON_STACK(desc, tfm); /* 스택에 shash_desc 할당 */
int ret;
if (IS_ERR(tfm))
return PTR_ERR(tfm);
desc->tfm = tfm;
ret = crypto_shash_digest(desc, data, len, digest);
shash_desc_zero(desc); /* 민감 데이터 스택 소거 */
crypto_free_shash(tfm);
return ret;
}
코드 설명
SHA-256 동기 해시(shash)의 두 가지 할당 패턴입니다 (crypto/shash.c, crypto/sha256_generic.c).
- crypto_alloc_shash("sha256", 0, 0)priority가 가장 높은 SHA-256 구현(SHA-NI > generic)을 자동 선택하여 tfm을 할당합니다. 두 번째·세 번째 인자(type, mask)는 0으로 지정하면 제한 없이 최적 구현을 찾습니다.
- kmalloc(sizeof(*desc) + crypto_shash_descsize(tfm))
shash_desc구조체 뒤에 알고리즘별 내부 상태(예: SHA-256의 중간 해시 값)를 위한 가변 크기 공간을 함께 할당합니다.descsize는 알고리즘마다 다릅니다. - crypto_shash_digest(desc, data, len, digest)init → update → final을 한 번에 수행하는 편의 함수입니다. 데이터가 단일 연속 버퍼일 때 가장 간결한 패턴입니다.
- SHASH_DESC_ON_STACK(desc, tfm)힙 대신 스택에
shash_desc를 할당하는 매크로입니다.kmalloc오버헤드가 없지만,descsize가 큰 알고리즘에서는 스택 오버플로 위험이 있으므로CONFIG_FRAME_WARN한도를 확인해야 합니다. - shash_desc_zero(desc)스택에 할당된 desc의 내부 상태(중간 해시 값)를 명시적으로 0으로 소거합니다. 민감 데이터의 스택 잔류를 방지하는 보안 조치입니다.
HMAC 사용 예제
HMAC(Hash-based Message Authentication Code)은 해시 함수에 비밀 키를 결합하여 메시지 인증 코드를 생성합니다. Crypto API에서 HMAC은 "hmac(sha256)"처럼 해시 알고리즘을 감싸는 템플릿으로 사용하며, shash 인터페이스를 그대로 사용합니다. 차이점은 crypto_shash_setkey()로 MAC 키를 설정해야 한다는 것뿐입니다:
#include <crypto/hash.h>
/* ━━━ HMAC-SHA256 메시지 인증 코드 생성 ━━━ */
static int calc_hmac_sha256(const u8 *key, unsigned int key_len,
const u8 *data, unsigned int data_len,
u8 *mac)
{
struct crypto_shash *tfm;
struct shash_desc *desc;
int ret;
/* hmac(sha256) 템플릿으로 tfm 할당 */
tfm = crypto_alloc_shash("hmac(sha256)", 0, 0);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
/* HMAC 키 설정 — shash와 달리 setkey가 필수 */
ret = crypto_shash_setkey(tfm, key, key_len);
if (ret)
goto out_free;
desc = kmalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL);
if (!desc) { ret = -ENOMEM; goto out_free; }
desc->tfm = tfm;
ret = crypto_shash_digest(desc, data, data_len, mac);
/* mac에 32바이트 HMAC-SHA256 결과 기록 */
kfree(desc);
out_free:
crypto_free_shash(tfm);
return ret;
}
코드 설명
HMAC-SHA256 메시지 인증 코드 생성 예제입니다 (crypto/hmac.c, include/crypto/hash.h).
- crypto_alloc_shash("hmac(sha256)", 0, 0)
hmac템플릿이sha256해시를 감싸는 복합 알고리즘을 할당합니다. 커널은 내부적으로hmac_init()/hmac_update()/hmac_final()을 연결하여, ipad/opad XOR 연산과 이중 해시를 자동 처리합니다. - crypto_shash_setkey(tfm, key, key_len)HMAC 키를 설정합니다. 키가 블록 크기(SHA-256은 64바이트)보다 길면 먼저 해시하여 축약합니다. 일반 해시(SHA-256 등)에는
setkey가 필요 없지만, HMAC/CMAC/XCBC 등 키 기반 MAC에서는 필수입니다. - crypto_shash_digest(desc, data, data_len, mac)내부적으로 HMAC(K, M) = H((K' ⊕ opad) || H((K' ⊕ ipad) || M))을 수행합니다. 단일 호출로 init→update→final 전체를 처리하며, 결과는 32바이트(SHA-256 출력 크기)입니다.
증분 해시 (init/update/final)
데이터가 여러 조각으로 나뉘어 있거나 스트리밍으로 도착하는 경우, digest() 한 번 호출 대신 init → update(반복) → final 3단계를 사용합니다. 이 패턴은 IMA 무결성 검증, 파일 해시, 네트워크 패킷 누적 해시 등에서 사용됩니다:
#include <crypto/hash.h>
/* ━━━ 증분 해시: 여러 조각 데이터를 순차적으로 해시 ━━━ */
static int hash_file_chunks(struct crypto_shash *tfm,
const u8 **chunks, const unsigned int *lens,
int nchunks, u8 *digest)
{
SHASH_DESC_ON_STACK(desc, tfm);
int i, ret;
desc->tfm = tfm;
/* 1단계: 초기화 — 내부 해시 상태를 IV로 설정 */
ret = crypto_shash_init(desc);
if (ret)
goto out;
/* 2단계: 데이터 조각별로 update — 중간 상태 누적 */
for (i = 0; i < nchunks; i++) {
ret = crypto_shash_update(desc, chunks[i], lens[i]);
if (ret)
goto out;
}
/* 3단계: 최종화 — 패딩 + 마지막 블록 처리 → 다이제스트 출력 */
ret = crypto_shash_final(desc, digest);
out:
shash_desc_zero(desc);
return ret;
}
/* 사용 예: 커널 모듈에서 분산된 데이터 해시 */
static int verify_integrity(void)
{
struct crypto_shash *tfm;
u8 digest[32];
const u8 *chunks[] = { header, body, trailer };
const unsigned int lens[] = { hdr_len, body_len, trl_len };
tfm = crypto_alloc_shash("sha256", 0, 0);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
hash_file_chunks(tfm, chunks, lens, 3, digest);
/* digest를 예상 값과 비교 */
crypto_free_shash(tfm);
return 0;
}
코드 설명
증분 해시(Incremental Hash)의 init/update/final 3단계 패턴입니다. IMA 무결성 검증(security/integrity/ima/), fscrypt 파일 해시 등에서 사용됩니다.
- crypto_shash_init(desc)해시 내부 상태를 초기값(IV)으로 설정합니다. SHA-256의 경우 8개의 32비트 초기 해시 값(H0~H7)을 FIPS 180-4 규격대로 초기화합니다.
- crypto_shash_update(desc, chunks[i], lens[i])데이터 조각을 내부 상태에 누적합니다. 블록 크기(SHA-256은 64바이트)에 도달할 때마다 압축 함수를 실행하고, 남은 부분은 내부 버퍼에 보관합니다. 몇 번이든 반복 호출할 수 있습니다.
- crypto_shash_final(desc, digest)내부 버퍼에 남은 데이터에 패딩(1비트 + 0 패딩 + 64비트 길이)을 추가하고 마지막 블록을 처리한 뒤, 최종 해시 값을
digest에 기록합니다. - shash_desc_zero(desc)스택에 할당된 desc의 중간 해시 상태를 소거합니다. 중간 상태가 노출되면 해시 연장 공격(Length Extension Attack)에 이용될 수 있으므로, 사용 후 즉시 소거하는 것이 보안상 권장됩니다.
shash vs digest() vs init/update/final 선택:
- 단일 연속 버퍼 →
crypto_shash_digest()— 가장 간결하고 최적화 가능성 높음 - 여러 조각 / 스트리밍 →
init()→update()반복 →final() - HMAC/CMAC → 동일한 shash API에
setkey()만 추가 - HW DMA 해시 엔진 →
ahash인터페이스 사용 (scatterlist 입력)
대칭 암호 예제
대칭 암호(skcipher)는 동일한 키로 암호화와 복호화를 수행합니다. crypto_skcipher API는 블록 암호 모드(CBC, CTR, XTS 등)와 스트림 암호(ChaCha20)를 통합 인터페이스로 제공합니다. 4단계 라이프사이클을 따릅니다:
#include <crypto/skcipher.h>
#include <linux/scatterlist.h>
/* ━━━ AES-CBC 대칭 암호화 전체 예제 (비동기 지원) ━━━ */
static int aes_cbc_encrypt(const u8 *key, unsigned int key_len,
u8 *iv, u8 *data, unsigned int data_len)
{
struct crypto_skcipher *tfm;
struct skcipher_request *req;
struct scatterlist sg;
u8 iv_local[16];
int ret;
DECLARE_CRYPTO_WAIT(wait);
/* ① tfm 할당 — 커널이 최적 구현을 자동 선택 */
tfm = crypto_alloc_skcipher("cbc(aes)", 0, 0);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
ret = crypto_skcipher_setkey(tfm, key, key_len);
if (ret)
goto out_free_tfm;
/* ② req 할당 + 콜백 설정 */
req = skcipher_request_alloc(tfm, GFP_KERNEL);
if (!req) { ret = -ENOMEM; goto out_free_tfm; }
skcipher_request_set_callback(req,
CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &wait);
/* ③ scatterlist + IV 설정 (in-place 암호화) */
sg_init_one(&sg, data, data_len);
memcpy(iv_local, iv, 16); /* IV 복사본 — 일부 드라이버가 원본 수정 */
skcipher_request_set_crypt(req, &sg, &sg, data_len, iv_local);
/* ④ 암호화 수행 — 동기·비동기 투명 처리 */
ret = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);
memzero_explicit(iv_local, sizeof(iv_local));
skcipher_request_free(req);
out_free_tfm:
crypto_free_skcipher(tfm);
return ret; /* 0: 성공, 음수: 에러 */
}
코드 설명
AES-CBC 대칭 암호화의 전체 라이프사이클입니다 (include/crypto/skcipher.h, crypto/skcipher.c).
- crypto_alloc_skcipher("cbc(aes)", 0, 0)커널이 등록된 모든
cbc(aes)구현 중 priority가 가장 높은 것을 선택합니다. x86에서는cbc-aes-aesni(pri=400), i.MX에서는cbc-aes-caam(pri=3000)이 자동 선택됩니다. - crypto_skcipher_setkey(tfm, key, key_len)AES 키 스케줄을 생성하여 tfm 내부에 저장합니다. key_len이 16/24/32가 아니면
-EINVAL을 반환합니다. 키 스케줄은 tfm 수명 동안 유지되므로, 동일 키로 반복 암호화 시 이 호출을 매번 반복할 필요가 없습니다. - DECLARE_CRYPTO_WAIT(wait)비동기 완료를 동기적으로 대기하기 위한
struct crypto_wait를 스택에 선언합니다. 내부에completion과 에러 코드를 담고 있습니다. - skcipher_request_set_callback(req, MAY_BACKLOG | MAY_SLEEP, ...)
MAY_BACKLOG: HW 큐 포화 시 백로그에 추가(거부하지 않음).MAY_SLEEP: 호출자가 process context임을 표시.crypto_req_done: 커널 내장 콜백으로 completion을 완료 시킵니다. - memcpy(iv_local, iv, 16)IV 복사본을 사용합니다. CBC 모드의 일부 드라이버는 암호화 과정에서 IV 버퍼를 최종 암호문 블록으로 in-place 갱신하므로, 원본 IV가 변경될 수 있습니다.
- sg_init_one(&sg, data, data_len)단일 연속 버퍼를 scatterlist 1개로 초기화합니다. src와 dst에 같은 sg를 지정하면 in-place 암호화(원본 버퍼를 암호문으로 덮어씀)가 됩니다.
- crypto_wait_req(crypto_skcipher_encrypt(req), &wait)encrypt()가 0을 반환하면 즉시 완료(동기),
-EINPROGRESS를 반환하면 콜백이 올 때까지 sleep합니다. 어떤 드라이버가 선택되든 이 한 줄로 동기·비동기 모두 투명하게 처리됩니다. - memzero_explicit(iv_local, sizeof(iv_local))스택의 IV 복사본을 명시적으로 소거합니다. 컴파일러 최적화로 일반
memset이 제거될 수 있으므로, 보안 민감 데이터에는 반드시memzero_explicit을 사용합니다.
| 주요 대칭 암호 모드 | 커널 이름 | 패딩 필요 | 병렬화 | 주요 사용처 |
|---|---|---|---|---|
| CBC (Cipher Block Chaining) | cbc(aes) | 예 (블록 정렬) | decrypt만 | 레거시 IPsec, dm-crypt |
| CTR (Counter) | ctr(aes) | 아니오 | 완전 병렬 | GCM 내부, 키 유도 |
| XTS (XEX Tweakable) | xts(aes) | 부분 (CTS) | 완전 병렬 | dm-crypt, fscrypt |
| ECB (Electronic Codebook) | ecb(aes) | 예 | 완전 병렬 | 키 래핑, 테스트용 |
| ChaCha20 (스트림) | chacha20 | 아니오 | 완전 병렬 | WireGuard 내부 |
| Adiantum | adiantum(xchacha12,aes) | 아니오 | 블록 단위 | 저사양 디바이스 fscrypt |
AES-XTS 디스크 암호화 예제
XTS(XEX-based Tweaked codebook mode with ciphertext Stealing)는 디스크 암호화(dm-crypt, fscrypt)의 표준 모드입니다. CBC와 달리 패딩이 불필요하고, 섹터 번호를 tweak으로 사용하여 동일 평문이라도 섹터마다 다른 암호문을 생성합니다. XTS는 키 크기가 일반 AES의 2배입니다(AES-256-XTS는 64바이트 키 사용):
#include <crypto/skcipher.h>
#include <linux/scatterlist.h>
/* ━━━ AES-XTS 섹터 암호화 (dm-crypt 패턴) ━━━ */
static int xts_encrypt_sector(struct crypto_skcipher *tfm,
u8 *sector_data, unsigned int sector_size,
u64 sector_num)
{
struct skcipher_request *req;
struct scatterlist sg;
u8 iv[16];
int ret;
DECLARE_CRYPTO_WAIT(wait);
req = skcipher_request_alloc(tfm, GFP_NOIO);
if (!req)
return -ENOMEM;
skcipher_request_set_callback(req,
CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &wait);
/* XTS IV: 섹터 번호를 리틀 엔디안으로 배치 */
memset(iv, 0, sizeof(iv));
*(__le64 *)iv = cpu_to_le64(sector_num);
sg_init_one(&sg, sector_data, sector_size);
skcipher_request_set_crypt(req, &sg, &sg, sector_size, iv);
ret = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);
skcipher_request_free(req);
return ret;
}
/* 초기화 예 (dm-crypt 스타일) */
static struct crypto_skcipher *setup_xts(const u8 *master_key)
{
struct crypto_skcipher *tfm;
/* xts(aes): AES-256-XTS는 64바이트 키 (암호화 키 32B + tweak 키 32B) */
tfm = crypto_alloc_skcipher("xts(aes)", 0, 0);
if (IS_ERR(tfm))
return tfm;
if (crypto_skcipher_setkey(tfm, master_key, 64)) {
crypto_free_skcipher(tfm);
return ERR_PTR(-EINVAL);
}
return tfm;
}
코드 설명
AES-XTS 디스크 섹터 암호화 패턴입니다. dm-crypt(drivers/md/dm-crypt.c)가 사용하는 방식과 동일합니다.
- crypto_alloc_skcipher("xts(aes)", 0, 0)XTS 모드는 내부적으로 AES를 두 번 사용합니다 — 데이터 암호화용 AES와 tweak 생성용 AES. 따라서 키 크기가 2배입니다 (AES-128-XTS=32B, AES-256-XTS=64B).
- GFP_NOIO블록 I/O 경로에서 호출되므로
GFP_KERNEL대신GFP_NOIO를 사용합니다. I/O 완료를 기다리는 중에 메모리 할당이 또 I/O를 유발하면 교착 상태가 발생할 수 있습니다. - *(__le64 *)iv = cpu_to_le64(sector_num)XTS의 tweak 값으로 섹터 번호를 사용합니다. 리틀 엔디안으로 IV의 처음 8바이트에 배치하고 나머지는 0입니다. 이를 통해 동일 평문이라도 섹터마다 다른 암호문이 생성되어, ECB 모드의 패턴 노출 문제를 방지합니다.
- sg_init_one(&sg, sector_data, sector_size)src와 dst에 동일한 sg를 지정하여 in-place 암호화를 수행합니다. dm-crypt는 바이오(bio) 페이지를 직접 암호화하여 zero-copy를 구현합니다.
Scatterlist 심화 — 물리 메모리 분산 I/O
Crypto API의 모든 데이터 전달은 scatterlist(SG)를 통해 이루어집니다. scatterlist는 물리적으로 분산된 메모리 페이지들을 연결 목록으로 표현하는 구조체로, DMA 친화적인 zero-copy 처리를 가능하게 합니다. 네트워크 패킷의 SKB 프래그먼트, 블록 I/O의 바이오벡(bvec), 파일시스템의 페이지 캐시 등 커널의 데이터는 대부분 비연속적이므로, SG 없이는 매번 memcpy로 연속 버퍼를 만들어야 합니다:
#include <linux/scatterlist.h>
/* ━━━ Scatterlist 구성 패턴 ━━━ */
/* 패턴 1: 단일 연속 버퍼 → SG 1개 */
struct scatterlist sg;
sg_init_one(&sg, buf, buf_len);
/* 패턴 2: 여러 버퍼 → SG 배열 */
struct scatterlist sg[3];
sg_init_table(sg, 3); /* 배열 초기화 + 마지막 엔트리 표시 */
sg_set_buf(&sg[0], header, hdr_len);
sg_set_buf(&sg[1], body, body_len);
sg_set_buf(&sg[2], trailer, trl_len);
/* 패턴 3: 페이지 기반 (네트워크/블록 I/O) */
sg_set_page(&sg[0], page, PAGE_SIZE, offset);
/* 패턴 4: in-place 암호화 (src == dst) */
skcipher_request_set_crypt(req, &sg, &sg, len, iv);
/* → src와 dst가 같으면 원본 버퍼를 직접 암호문으로 덮어씀 */
/* 패턴 5: 별도 출력 버퍼 (src ≠ dst) */
struct scatterlist src_sg, dst_sg;
sg_init_one(&src_sg, plaintext, len);
sg_init_one(&dst_sg, ciphertext, len);
skcipher_request_set_crypt(req, &src_sg, &dst_sg, len, iv);
Scatterlist 주의사항:
- DMA 매핑 필수: HW 가속기가 SG를 사용하려면 물리 주소가 DMA 매핑되어 있어야 합니다.
kmalloc버퍼는 자동으로 DMA 가능하지만,vmalloc버퍼나 스택 버퍼는 DMA에 사용할 수 없습니다. - 비동기 중 수정 금지:
-EINPROGRESS반환 후 콜백이 올 때까지 SG가 참조하는 모든 페이지/버퍼를 해제하거나 수정하면 안 됩니다 — HW가 DMA로 접근 중입니다. - SG 체이닝: 65개 이상의 세그먼트가 필요하면
sg_chain()으로 SG 테이블을 연결합니다. 대부분의 Crypto API 드라이버가 체이닝을 지원합니다.
하드웨어 가속
Crypto API의 priority 기반 자동 선택 메커니즘을 통해, 사용자 코드 변경 없이 하드웨어 가속이 투명하게 적용됩니다. 동일 알고리즘의 여러 구현이 등록되면 priority가 가장 높은 것이 자동 선택됩니다:
| 가속 유형 | 대표 하드웨어 | Priority 범위 | 특징 | 상세 섹션 |
|---|---|---|---|---|
| CPU ISA 명령어 | AES-NI, SHA-NI, ARM CE, CRC32C | 300~400 | 최저 지연, 소량 데이터 최적, CPU 코어 점유 | AES-NI, ARM 가속 |
| PCI 가속기 | Intel QAT, AMD CCP, Marvell NITROX | 200~4001 | 고처리량, PCIe DMA, 배치 처리, CPU 부하 분산 | QAT, CCP |
| SoC 임베디드 | NXP CAAM, Marvell CPT, HiSilicon SEC | 3000~4001 | SoC 내장, 초저지연, 임베디드/네트워크 특화 | CAAM, CPT |
| 가상 디바이스 | virtio-crypto | 가변 | VM에서 호스트 가속기 활용 | virtio-crypto |
# ━━━ 하드웨어 가속 상태 확인 ━━━
# 등록된 모든 알고리즘과 드라이버·우선순위 확인
grep -E "^name|^driver|^priority|^type" /proc/crypto | head -20
# name : cbc(aes)
# driver : cbc-aes-aesni ← AES-NI 가속
# priority : 400
# type : skcipher
# CPU 암호 명령어 지원 확인
grep -oE "aes|sha_ni|pclmulqdq|crc32c" /proc/cpuinfo | sort -u
# aes pclmulqdq sha_ni crc32c
# ARM CE 확인
grep -oE "aes|sha2|pmull|crc32" /proc/cpuinfo | sort -u
# HW 가속기 모듈 로드 확인
lsmod | grep -E "aesni|caam|qat|otx2_cpt|hisi_sec"
투명한 가속의 의미: crypto_alloc_skcipher("cbc(aes)", 0, 0)를 호출하는 코드는 AES-NI가 있는 x86 서버에서든, CAAM이 있는 i.MX 보드에서든, 가속기 없는 RISC-V에서든 동일하게 동작합니다. 커널이 사용 가능한 최적 구현을 자동 선택하므로, 사용자 코드에 하드웨어 종속적인 분기가 불필요합니다.
비동기 암호화 (Async Crypto)
커널 Crypto API의 비동기(Asynchronous) 처리 모델은 HW 가속기와 CPU를 병렬로 동작시키는 핵심 메커니즘입니다. 동기(synchronous) 알고리즘(AES-NI 등)은 함수가 반환되면 이미 완료되지만, 비동기 알고리즘(QAT, CAAM, CPT 등)은 요청을 큐에 제출한 뒤 즉시 반환하고, HW가 완료되면 콜백(callback)으로 결과를 통지합니다. 이 차이를 이해하지 않으면 데이터 손상, deadlock, use-after-free 등 치명적 버그가 발생합니다.
동기 vs 비동기 처리 모델
Crypto API의 모든 알고리즘은 /proc/crypto의 async 필드로 동기/비동기 여부를 확인할 수 있습니다. 동일한 알고리즘 이름(cbc(aes) 등)에 대해 동기·비동기 구현이 공존하며, priority가 높은 구현이 자동 선택됩니다:
# /proc/crypto에서 동기·비동기 구분 확인
grep -A4 "^name.*cbc(aes)" /proc/crypto
# name : cbc(aes)
# driver : cbc-aes-aesni ← AES-NI (동기)
# priority : 400
# async : no ← 동기: 즉시 완료
# ---
# name : cbc(aes)
# driver : cbc-aes-caam ← CAAM (비동기)
# priority : 3000
# async : yes ← 비동기: 콜백으로 완료
# CAAM priority(3000) > AES-NI priority(400)
# → i.MX/Layerscape에서 crypto_alloc_skcipher("cbc(aes)")는 CAAM을 자동 선택
반환값 계약과 완료 통지
Crypto API 비동기 모델에서 가장 중요한 것은 반환값과 콜백의 관계입니다. crypto_skcipher_encrypt(), crypto_aead_encrypt(), crypto_ahash_digest() 등 모든 비동기 API 함수는 동일한 반환값 규약을 따릅니다:
| 반환값 | 의미 | 콜백 호출 여부 | 호출자가 해야 할 일 |
|---|---|---|---|
0 | 즉시 완료 (동기적으로 처리됨) | 호출 안 됨 | 결과 데이터를 바로 사용 |
-EINPROGRESS | HW에 제출됨, 나중에 완료 | 반드시 호출됨 | 콜백에서 결과 처리 (req 접근 금지) |
-EBUSY | 큐 포화, 백로그(backlog)에 추가 | 반드시 호출됨 (2회) | 첫 콜백(err=-EINPROGRESS)=큐 진입, 두 번째 콜백=완료 |
-EINVAL | 파라미터 오류 (키/IV/길이) | 호출 안 됨 | 에러 처리 |
-ENOMEM | 메모리 할당 실패 | 호출 안 됨 | 에러 처리 또는 재시도 |
-EBADMSG | AEAD 인증 태그 불일치 | 상황에 따라 다름 | 동기 실패면 직접 처리, 비동기면 콜백에서 처리 |
가장 흔한 치명적 버그 3가지:
① 0 반환 후 콜백 호출: 이미 완료된 요청에 대해 콜백이 다시 불리면 상위 계층이 중복 완료(double completion)로 패닉하거나 데이터를 손상시킵니다.
② -EINPROGRESS 반환 후 req 즉시 해제: HW가 아직 DMA로 req 메모리에 접근 중인데 해제하면 use-after-free가 발생합니다. 콜백이 올 때까지 req를 유지해야 합니다.
③ -EBUSY 무시: 백로그 콜백을 처리하지 않으면 요청이 영원히 완료되지 않아 메모리 누수와 deadlock이 발생합니다.
콜백 메커니즘 상세
비동기 콜백은 skcipher_request_set_callback() / aead_request_set_callback() / ahash_request_set_callback()으로 등록합니다. 콜백 함수의 시그니처와 호출 컨텍스트를 이해하는 것이 안전한 비동기 코드 작성의 핵심입니다:
/* ━━━ 비동기 콜백 함수 작성 패턴 ━━━ */
/* 콜백 함수 시그니처 — 모든 비동기 API 공통 */
static void my_crypto_done(void *data, int err)
{
struct my_async_result *result = data;
/* 백로그 경로: 1차 콜백은 "큐 진입 알림"일 뿐 */
if (err == -EINPROGRESS)
return; /* 아직 완료 아님, 2차 콜백을 기다림 */
/* 여기가 진짜 완료 지점 — err=0이면 성공, 음수면 에러 */
result->err = err;
complete(&result->completion);
}
/* 콜백 결과를 전달받을 구조체 */
struct my_async_result {
struct completion completion;
int err;
};
crypto_wait_req() — 비동기를 동기처럼 사용하기
process context(sleep 가능)에서는 콜백 기반 상태 머신을 직접 구현할 필요 없이 DECLARE_CRYPTO_WAIT와 crypto_wait_req()를 사용하면 동기·비동기 양쪽을 동일한 코드로 처리할 수 있습니다. 이 패턴은 dm-crypt, fscrypt, IKE 등 커널 내 대부분의 Crypto API 소비자가 사용합니다:
/* ━━━ crypto_wait_req() 사용 예 — 가장 간결한 비동기 패턴 ━━━ */
#include <crypto/skcipher.h>
static int encrypt_data(struct crypto_skcipher *tfm,
u8 *data, unsigned int len, u8 *iv)
{
struct skcipher_request *req;
struct scatterlist sg;
DECLARE_CRYPTO_WAIT(wait); /* completion + err 포함 */
int ret;
req = skcipher_request_alloc(tfm, GFP_KERNEL);
if (!req)
return -ENOMEM;
sg_init_one(&sg, data, len);
skcipher_request_set_crypt(req, &sg, &sg, len, iv);
skcipher_request_set_callback(req,
CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &wait); /* 커널 내장 콜백 */
/* 한 줄로 동기·비동기 모두 처리 */
ret = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);
/* ret == 0: 성공 (동기든 비동기든 무관) */
/* ret < 0: 에러 (암호화 실패) */
skcipher_request_free(req);
return ret;
}
비동기 요청 라이프사이클
비동기 암호 요청은 4단계 라이프사이클을 거칩니다. 각 단계에서 메모리 소유권이 이동하므로 순서를 지키는 것이 중요합니다:
완료 통지 모델 3종 비교
HW 가속기가 암호 연산을 완료한 뒤 결과를 CPU에 통지하는 방식은 3가지입니다. 워크로드 특성에 따라 최적 모델이 달라집니다:
| 항목 | 동기 (Synchronous) | 비동기 인터럽트 (Async IRQ) | 비동기 폴링 (Async Polling) |
|---|---|---|---|
| 처리 주체 | CPU 자체 (AES-NI/CE) | HW 가속기 + IRQ | HW 가속기 + CPU 폴링 |
| 완료 통지 | 함수 반환 (ret=0) | 인터럽트 → softirq 콜백 | CPU가 completion 레지스터 폴링 |
| CPU 점유 | 연산 중 100% 점유 | 제출·콜백만 — 중간에 자유 | 폴링 루프로 코어 전용 |
| 지연 시간 | 최저 (DMA 없음) | 중간 (IRQ 지연 5-50μs) | 최저 (즉시 감지, ~1μs) |
| 처리량 | CPU 속도에 비례 | 높음 (병렬 처리) | 최고 (IRQ 오버헤드 없음) |
| 전력 효율 | 중간 | 높음 (유휴 시 절전) | 낮음 (코어 100% 점유) |
| 적합 시나리오 | 소형 패킷, SW 전용 | 범용 HW 가속 (dm-crypt 등) | DPDK/VPP 데이터 플레인 |
| /proc/crypto | async: no | async: yes | async: yes |
| 대표 드라이버 | aesni-intel, ghash-clmulni | qat, caam, otx2-cpt | DPDK cryptodev (UIO) |
백로그(Backlog) 메커니즘
HW 가속기의 하드웨어 큐는 유한합니다. 큐가 가득 찬 상태에서 새 요청이 들어오면, CRYPTO_TFM_REQ_MAY_BACKLOG 플래그가 설정된 경우 커널은 요청을 소프트웨어 백로그 리스트에 추가하고 -EBUSY를 반환합니다. HW 큐에 빈 슬롯이 생기면 백로그에서 요청을 꺼내 자동으로 재제출합니다:
/* ━━━ 백로그 처리가 포함된 완전한 비동기 패턴 ━━━ */
/* 콜백 — 백로그를 올바르게 처리 */
static void my_crypto_complete(void *data, int err)
{
struct my_ctx *ctx = data;
if (err == -EINPROGRESS) {
/* 백로그 경로 1차 콜백: 큐에 진입한 알림
* → 아직 처리 완료가 아니므로 데이터에 접근하면 안 됨
* → 필요하면 통계 카운터만 업데이트 */
atomic_dec(&ctx->backlog_count);
return;
}
/* 진짜 완료 — err=0이면 성공 */
ctx->err = err;
complete(&ctx->done);
}
/* 제출 측 */
int ret = crypto_skcipher_encrypt(req);
switch (ret) {
case 0:
/* 즉시 완료 — 결과 사용 가능 */
process_result(req);
break;
case -EINPROGRESS:
/* HW에서 처리 중 — 콜백으로 완료 통지 */
break;
case -EBUSY:
/* 큐 포화 → 백로그 진입 — 결국 콜백으로 완료 */
atomic_inc(&ctx->backlog_count);
break;
default:
/* 에러 (콜백 없음) — 직접 에러 처리 */
handle_error(ret);
break;
}
crypto_engine — HW 드라이버의 비동기 큐 관리
crypto_engine은 커널이 HW 가속기 드라이버를 위해 제공하는 비동기 큐 관리 프레임워크입니다. 드라이버가 직접 큐잉·백로그·완료 통지를 구현하는 대신, crypto_engine에 위임하면 Crypto API 규약에 맞는 비동기 처리가 자동으로 이루어집니다:
crypto_engine을 사용하는 주요 드라이버: STM32 CRYP, Allwinner sun8i-ce, Rockchip crypto, ATMEL AES/SHA 등 중소형 SoC 가속기 드라이버가 crypto_engine을 사용합니다. CAAM, QAT, CPT 같은 고성능 드라이버는 자체 큐 관리를 구현합니다.
crypto_engine vs 자체 큐: crypto_engine은 단일 kworker 스레드로 요청을 직렬 처리하므로 구현이 단순하지만, 멀티큐 병렬 처리가 필요한 고성능 가속기에는 적합하지 않습니다. CAAM의 잡 링이나 CPT의 IQ처럼 HW 자체에 다중 큐가 있는 경우 crypto_queue를 직접 관리하는 것이 성능에 유리합니다. 자체 큐 구현의 상세는 crypto_queue를 직접 쓰는 드라이버 섹션을, 반환값 계약의 상세는 비동기 드라이버의 반환값 계약 섹션을 참고하세요.
비동기 패턴 실전 코드: crypto_wait_req()를 사용하는 전체 예제는 비동기 구현을 동기처럼 다루는 skcipher 패턴, ahash 비동기 예제는 ahash와 멀티 세그먼트 scatterlist 섹션을 참고하세요.
AEAD (Authenticated Encryption with Associated Data)
AEAD(Authenticated Encryption with Associated Data)는 암호화(기밀성)와 인증(무결성·진본성)을 단일 원자적 연산으로 결합하는 암호 프리미티브입니다. 전통적인 "encrypt-then-MAC" 조합과 달리 키 관리가 단순하고, 암호화·인증 순서 실수로 인한 취약점(Padding Oracle 등)이 구조적으로 불가능합니다. 커널에서 IPsec ESP, kTLS, WireGuard, dm-integrity+dm-crypt 등 거의 모든 현대 암호 프로토콜이 AEAD를 사용합니다.
AEAD 동작 원리
AEAD 알고리즘은 4개의 입력을 받아 암호문과 인증 태그(Authentication Tag)를 생성합니다:
커널에서 지원하는 주요 AEAD 알고리즘
| 알고리즘 | 커널 이름 | Nonce/IV | 태그 크기 | 주요 사용처 | HW 가속 |
|---|---|---|---|---|---|
| AES-GCM | gcm(aes) | 12B (96비트) | 16B | IPsec ESP, kTLS, dm-crypt | AES-NI+PCLMULQDQ, QAT, CAAM, CPT |
| AES-CCM | ccm(aes) | 7~13B (가변) | 4~16B | Bluetooth, IEEE 802.15.4 | AES-NI, ARM CE |
| ChaCha20-Poly1305 | rfc7539(chacha20,poly1305) | 12B | 16B | WireGuard, TLS 1.3 | AVX2/AVX-512, ARM NEON |
| AES-GCM (RFC4106) | rfc4106(gcm(aes)) | 8B (ESP 시퀀스) | 16B | IPsec ESP 전용 | AES-NI, QAT, CAAM, CPT |
| AES-GCM (RFC4543) | rfc4543(gcm(aes)) | 8B | 16B | IPsec AH (GMAC) | AES-NI |
| authenc(HMAC,CBC) | authenc(hmac(sha256),cbc(aes)) | 16B (CBC IV) | 32B (HMAC) | 레거시 IPsec, dm-crypt | AES-NI + SHA-NI |
| Aegis-128 | aegis128 | 16B | 16B | 고속 소프트웨어 AEAD | AES-NI (전용 최적화) |
scatterlist 메모리 레이아웃
AEAD 요청에서 가장 실수가 많은 부분이 scatterlist 레이아웃입니다. encrypt와 decrypt에서 src/dst의 구성이 비대칭적이며, cryptlen의 의미도 다릅니다:
AEAD API 사용 패턴
/* ━━━ AEAD (AES-GCM) 암호화/복호화 전체 예제 ━━━ */
#include <crypto/aead.h>
#include <linux/scatterlist.h>
#define GCM_IV_SIZE 12 /* AES-GCM 표준 IV */
#define GCM_TAG_SIZE 16 /* 128-bit 인증 태그 */
/* ── 암호화 ── */
static int aead_encrypt_example(const u8 *key, unsigned int key_len,
const u8 *iv, const u8 *aad, unsigned int aad_len,
const u8 *plaintext, unsigned int pt_len,
u8 *output) /* output 크기 = aad_len + pt_len + GCM_TAG_SIZE */
{
struct crypto_aead *tfm;
struct aead_request *req;
struct scatterlist src_sg[2], dst_sg[3];
u8 iv_local[GCM_IV_SIZE];
int ret;
DECLARE_CRYPTO_WAIT(wait);
/* 1. tfm 할당 + 키·태그 크기 설정 */
tfm = crypto_alloc_aead("gcm(aes)", 0, 0);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
ret = crypto_aead_setkey(tfm, key, key_len);
if (ret) goto out_free_tfm;
ret = crypto_aead_setauthsize(tfm, GCM_TAG_SIZE);
if (ret) goto out_free_tfm;
/* 2. request 할당 + 콜백 설정 */
req = aead_request_alloc(tfm, GFP_KERNEL);
if (!req) { ret = -ENOMEM; goto out_free_tfm; }
aead_request_set_callback(req,
CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &wait);
/* 3. scatterlist 구성 */
/* src: [AAD | Plaintext] */
sg_init_table(src_sg, 2);
sg_set_buf(&src_sg[0], aad, aad_len);
sg_set_buf(&src_sg[1], plaintext, pt_len);
/* dst: [AAD | Ciphertext | Tag] */
sg_init_table(dst_sg, 3);
sg_set_buf(&dst_sg[0], output, aad_len); /* AAD 복사 영역 */
sg_set_buf(&dst_sg[1], output + aad_len, pt_len); /* 암호문 */
sg_set_buf(&dst_sg[2], output + aad_len + pt_len, GCM_TAG_SIZE); /* 태그 */
/* 4. 요청 파라미터 설정 */
memcpy(iv_local, iv, GCM_IV_SIZE);
aead_request_set_crypt(req, src_sg, dst_sg, pt_len, iv_local);
/* ^^^^^^ Encrypt: 평문 길이 */
aead_request_set_ad(req, aad_len);
/* 5. 암호화 수행 (비동기 지원) */
ret = crypto_wait_req(crypto_aead_encrypt(req), &wait);
aead_request_free(req);
out_free_tfm:
crypto_free_aead(tfm);
return ret;
}
/* ── 복호화 ── */
static int aead_decrypt_example(const u8 *key, unsigned int key_len,
const u8 *iv, const u8 *aad, unsigned int aad_len,
const u8 *ct_and_tag, unsigned int ct_len,
u8 *output) /* output 크기 = aad_len + ct_len */
{
/* ... tfm/req 할당 동일 ... */
/* src: [AAD | Ciphertext | Tag] */
aead_request_set_crypt(req, src_sg, dst_sg,
ct_len + GCM_TAG_SIZE, iv_local);
/* ^^^^^^^^^^^^^^^^^^^^^^^^ Decrypt: 암호문 + 태그 길이! */
aead_request_set_ad(req, aad_len);
ret = crypto_wait_req(crypto_aead_decrypt(req), &wait);
/* ret == -EBADMSG: 인증 태그 불일치 → 데이터 위변조 감지 */
/* ret == 0: 복호화 성공, output에 평문 기록됨 */
}
프로토콜별 AEAD 적용 구조
커널의 주요 네트워크·스토리지 프로토콜은 각각 다른 방식으로 AEAD를 적용합니다. 아래 다이어그램은 각 프로토콜이 AAD, 평문, 태그를 어떻게 배치하는지 보여줍니다:
AES-GCM 내부 동작
AES-GCM은 커널에서 가장 많이 사용되는 AEAD 알고리즘입니다. 내부적으로 AES-CTR(Counter Mode)로 암호화하고, GHASH(Galois Hash)로 인증 태그를 생성합니다. AES-NI와 PCLMULQDQ(캐리 없는 곱셈) 명령어를 사용하면 두 연산을 파이프라인으로 병렬 처리할 수 있습니다:
authenc(HMAC,CBC) vs 네이티브 AEAD 비교
커널의 authenc 템플릿은 기존 블록 암호(CBC)와 MAC(HMAC)을 조합하여 AEAD 인터페이스를 제공하는 호환 레이어입니다. 네이티브 AEAD(GCM, ChaCha20-Poly1305)와의 핵심 차이를 이해하면 적절한 알고리즘을 선택할 수 있습니다:
| 항목 | authenc(hmac(sha256),cbc(aes)) | gcm(aes) | rfc7539(chacha20,poly1305) |
|---|---|---|---|
| 내부 구조 | CBC 암호화 → HMAC 생성 (2-pass) | CTR + GHASH 병렬 (1-pass) | ChaCha20 + Poly1305 (1-pass) |
| 키 크기 | 48B (AES-256 32B + HMAC 16B) | 16/32B (AES-128/256) | 32B |
| Nonce 크기 | 16B (CBC IV) | 12B | 12B |
| 태그 크기 | 32B (SHA-256 출력) | 16B | 16B |
| 패딩 | 필요 (PKCS#7) | 불필요 | 불필요 |
| Padding Oracle | 취약 가능 (구현 주의) | 구조적 불가 | 구조적 불가 |
| HW 병렬화 | 2-pass로 비효율 | CTR+GHASH 파이프라인 | SW 최적화 우수 |
| 성능 (AES-NI) | ~2-3 GB/s | ~5-10 GB/s | ~3-5 GB/s |
| 사용처 | 레거시 IPsec, 구형 TLS | IPsec, kTLS, dm-crypt | WireGuard, TLS 1.3 |
| 권장 | 호환용만, 신규 설계 지양 | HW AES 있으면 최적 | HW AES 없으면 최적 |
Nonce 재사용의 위험: AES-GCM에서 동일 키로 동일 Nonce를 두 번 사용하면 XOR 차분으로 두 평문의 관계가 노출되고, GHASH 서브키(H)가 복구되어 인증이 완전히 무력화됩니다. IPsec ESP는 시퀀스 번호를 Nonce로 사용하여 자동 방지하고, kTLS도 레코드 시퀀스를 사용합니다. 직접 구현 시 카운터 기반 Nonce 생성이 필수입니다.
참고: AEAD 상태 머신의 encrypt/decrypt 경로 전이는 AEAD 상태 머신, authenc 내부 구현 분석은 authenc 내부 구현 분석, scatterlist 구성 상세 예제는 AEAD scatterlist 메모리 레이아웃, 레코드 처리 코드 패턴은 AEAD 레코드 처리 패턴 섹션을 참고하세요.
커널 난수 생성기
암호화 시스템의 안전성은 키·IV·Nonce의 예측 불가능성에 달려 있으며, 이를 제공하는 것이 난수 생성기(Random Number Generator)입니다. 리눅스 커널은 다계층 난수 아키텍처를 사용합니다: 하드웨어 엔트로피 소스(TRNG/hwrng)가 물리적 무작위성을 수집하고, 커널 엔트로피 풀이 이를 축적한 뒤, ChaCha20 기반 CRNG(Cryptographically Secure PRNG)가 고속으로 난수를 생성합니다. Crypto API의 crypto_rng 인터페이스는 NIST SP 800-90A DRBG를 FIPS 호환 방식으로 제공합니다.
커널 난수 아키텍처
난수 API 비교 — 언제 무엇을 쓸 것인가
| API | 헤더 | 용도 | 컨텍스트 | FIPS 호환 | 성능 |
|---|---|---|---|---|---|
get_random_bytes() | <linux/random.h> | 범용 암호학적 난수 (키, IV, Nonce) | 어디서든 | 아니오 | 매우 빠름 (per-CPU) |
get_random_u32() / u64() | <linux/random.h> | 정수 난수 (해시 시드, 주소 무작위화) | 어디서든 | 아니오 | 매우 빠름 (배치) |
get_random_u32_below(n) | <linux/random.h> | 범위 제한 난수 (0~n-1) | 어디서든 | 아니오 | 매우 빠름 |
crypto_rng (DRBG) | <crypto/rng.h> | FIPS DRBG, 테스트 벡터 재현 | process ctx | 예 | 보통 |
getrandom(2) | syscall | 유저스페이스 암호 난수 | 유저 | 아니오 | 빠름 (vDSO 가능) |
/dev/urandom | - | 유저스페이스 레거시 인터페이스 | 유저 | 아니오 | 빠름 |
/dev/random | - | /dev/urandom과 동일 (6.x+) | 유저 | 아니오 | 빠름 |
/dev/hwrng | - | HW TRNG 직접 읽기 | 유저 | 소스에 따라 | 느림 (HW 속도) |
일반 코드에서는 항상 get_random_bytes()를 사용하세요. crypto_rng는 FIPS 경계 안에서 특정 DRBG를 명시적으로 선택해야 하거나 테스트 벡터를 재현해야 할 때만 필요합니다. 대부분의 커널 서브시스템(dm-crypt, IPsec, WireGuard 등)은 get_random_bytes()로 키와 Nonce를 생성합니다.
커널 공간 난수 API 사용 예제
#include <linux/random.h>
/* ━━━ 범용 암호학적 난수 (가장 일반적) ━━━ */
u8 aes_key[32];
get_random_bytes(aes_key, sizeof(aes_key)); /* 256-bit AES 키 */
u8 iv[16];
get_random_bytes(iv, sizeof(iv)); /* CBC IV */
u8 nonce[12];
get_random_bytes(nonce, sizeof(nonce)); /* GCM Nonce */
/* ━━━ 정수 난수 ━━━ */
u32 hash_seed = get_random_u32(); /* 해시 테이블 시드 */
u64 cookie = get_random_u64(); /* TCP SYN 쿠키 */
u32 idx = get_random_u32_below(100); /* 0~99 범위 */
/* ━━━ 초기 부팅 시 주의사항 ━━━ */
/* 부팅 초기에는 엔트로피 풀이 충분히 채워지지 않을 수 있음.
* get_random_bytes()는 CRNG가 초기화된 후에만 완전한 품질을 보장.
* wait_for_random_bytes()로 초기화 완료를 기다릴 수 있음 */
ret = wait_for_random_bytes(); /* CRNG 준비될 때까지 대기 */
if (ret)
pr_warn("CRNG not ready, random may be weak\n");
Crypto API DRBG — FIPS 호환 난수 생성
Crypto API의 crypto_rng 인터페이스는 NIST SP 800-90A 규격의 DRBG(Deterministic Random Bit Generator)를 제공합니다. 일반 용도에서는 get_random_bytes()가 충분하지만, FIPS 인증 환경이나 테스트 벡터 재현이 필요한 경우 DRBG를 직접 사용합니다:
| DRBG 알고리즘 | 커널 이름 | 내부 암호 | 예측 저항 | 용도 |
|---|---|---|---|---|
| HMAC-DRBG SHA-256 | drbg_nopr_hmac_sha256 | HMAC-SHA256 | 없음 (nopr) | FIPS 기본, 테스트 벡터 |
| HMAC-DRBG SHA-512 | drbg_nopr_hmac_sha512 | HMAC-SHA512 | 없음 | 고보안 FIPS |
| CTR-DRBG AES-256 | drbg_nopr_ctr_aes256 | AES-256-CTR | 없음 | AES-NI 가속 FIPS |
| HMAC-DRBG SHA-256 (PR) | drbg_pr_hmac_sha256 | HMAC-SHA256 | 있음 (pr) | 최고 보안 (매번 reseed) |
| CTR-DRBG AES-256 (PR) | drbg_pr_ctr_aes256 | AES-256-CTR | 있음 | FIPS + AES 가속 |
#include <crypto/rng.h>
/* ━━━ Crypto API DRBG 사용 예제 ━━━ */
static int generate_fips_random(u8 *out, unsigned int len)
{
struct crypto_rng *drbg;
u8 seed[64];
int ret;
/* 1. DRBG 인스턴스 할당 */
drbg = crypto_alloc_rng("drbg_nopr_hmac_sha256", 0, 0);
if (IS_ERR(drbg))
return PTR_ERR(drbg);
/* 2. 엔트로피 시드 준비 → DRBG에 주입 */
get_random_bytes(seed, crypto_rng_seedsize(drbg));
ret = crypto_rng_reset(drbg, seed, crypto_rng_seedsize(drbg));
if (ret)
goto out;
/* 3. DRBG에서 난수 생성 */
ret = crypto_rng_get_bytes(drbg, out, len);
/* ret == 0: 성공, -EINVAL: 파라미터 오류 */
out:
memzero_explicit(seed, sizeof(seed));
crypto_free_rng(drbg);
return ret;
}
엔트로피 소스와 HW RNG
커널이 수집하는 엔트로피 소스와 각각의 특성입니다. 다중 소스를 혼합(mixing)하여 단일 소스의 결함이 전체 시스템을 위협하지 않도록 합니다:
| 소스 | 메커니즘 | 엔트로피 품질 | 속도 | 가용성 |
|---|---|---|---|---|
| RDRAND/RDSEED | CPU 내장 디지털 노이즈 | 높음 (직접 HW) | ~800 MB/s | Intel Ivy Bridge+ / AMD Zen+ |
| hwrng (TRNG) | SoC 내장 열잡음/진동 | 높음 | 1~100 MB/s | CAAM RNG, STM32 RNG, TPM |
| 인터럽트 지터 | IRQ 도착 타이밍 차이 | 중간 | 느림 | 항상 (부팅 초기 핵심) |
| 디스크 I/O | seek/완료 타이밍 | 중간 | 느림 | 블록 디바이스 있을 때 |
| 입력 디바이스 | 키보드/마우스 타이밍 | 낮음~중간 | 간헐적 | 데스크탑 환경 |
| Jitter Entropy | CPU 실행 시간 변동 | 중간 | ~1 MB/s | 항상 (v5.18+ 내장) |
| 부트로더 시드 | EFI 변수 / DT | 높음 (이전 세션) | 1회 | EFI / DT 지원 시 |
ChaCha20 CRNG 내부 구조
리눅스 6.x의 핵심 CRNG는 ChaCha20 스트림 암호 기반입니다. CPU별(per-CPU) 독립 상태를 유지하여 lock-free 고속 생성이 가능합니다:
/* drivers/char/random.c — ChaCha20 CRNG 핵심 구조 (간소화) */
struct crng_state {
u32 state[16]; /* ChaCha20 상태 (512비트) */
unsigned long birth; /* 마지막 reseed 시각 */
};
/* per-CPU CRNG — 각 CPU가 독립 상태를 보유 */
static DEFINE_PER_CPU(struct crng_state, crngs);
/* get_random_bytes() 내부 동작 (간소화) */
void get_random_bytes(void *buf, size_t len)
{
struct crng_state *crng = this_cpu_ptr(&crngs);
/* 1. reseed 필요 여부 확인 (5분 또는 256B 출력마다) */
if (crng_needs_reseed(crng))
crng_reseed(crng); /* 엔트로피 풀에서 256비트 시드 추출 → 키 갱신 */
/* 2. ChaCha20 블록 생성 → buf에 복사 */
chacha20_block(crng->state, buf);
/* 3. backtrack 방지: 키 부분을 출력에서 즉시 갱신 */
crng_fast_key_erasure(crng->state);
/* → 과거 출력에서 키를 역추적하는 것이 불가능 */
}
fast key erasure: CRNG은 난수 블록을 생성할 때마다 키의 일부를 출력 데이터로 갱신합니다. 이를 통해 공격자가 현재 CRNG 상태를 탈취하더라도 과거에 생성된 난수를 역추적(backtrack)할 수 없습니다. 이것은 커널 CRNG의 핵심 보안 특성입니다.
/dev/random vs /dev/urandom (6.x): 커널 6.x부터 /dev/random과 /dev/urandom은 동일한 CRNG에서 출력합니다. 과거의 "엔트로피 소진 시 블로킹" 동작은 제거되었습니다. getrandom(2) 시스템 콜은 CRNG 초기화 전에만 블로킹하며, 초기화 후에는 항상 즉시 반환합니다.
참고: hwrng 서브시스템 아키텍처, QRNG 드라이버 개발, 엔트로피 풀 BLAKE2s 메커니즘, ChaCha20-CRNG 상태 갱신 상세는 Linux 하드웨어 난수 생성기 (hwrng & QRNG) 페이지에서 심층적으로 다룹니다. Crypto API의 DRBG 드라이버 구현은 RNG 드라이버 구현 섹션, DRBG를 직접 다루는 코드 패턴은 crypto_rng로 DRBG를 직접 다루는 패턴 섹션을 참고하세요.
커널 설정 옵션 (Kconfig)
Crypto Framework 기능은 CONFIG_CRYPTO_* 옵션으로 세밀하게 제어됩니다. 필요한 알고리즘과 드라이버만 선택하면 커널 크기를 줄이고 공격 표면을 최소화할 수 있습니다:
| 설정 옵션 | 용도 | 의존성/비고 |
|---|---|---|
CONFIG_CRYPTO | Crypto API 코어 프레임워크 | 거의 모든 커널 기능이 의존 |
CONFIG_CRYPTO_AES | AES 블록 암호 (generic) | SW 구현, fallback 용도 |
CONFIG_CRYPTO_AES_NI_INTEL | AES-NI 가속 (x86) | CONFIG_X86 + AES CPU 플래그 |
CONFIG_CRYPTO_AES_ARM64_CE | ARM CE 가속 (arm64) | CONFIG_ARM64 + CE 지원 |
CONFIG_CRYPTO_SHA256 | SHA-256 해시 (generic) | 모듈 서명, IMA 등 필수 |
CONFIG_CRYPTO_GCM | AES-GCM AEAD 모드 | IPsec, kTLS 필수 |
CONFIG_CRYPTO_XTS | XTS 디스크 암호화 모드 | dm-crypt, fscrypt 필수 |
CONFIG_CRYPTO_CHACHA20POLY1305 | ChaCha20-Poly1305 AEAD | WireGuard 필수 |
CONFIG_CRYPTO_HMAC | HMAC 템플릿 | IPsec, 키 유도 등 필수 |
CONFIG_CRYPTO_DRBG | NIST DRBG 난수 생성 | FIPS 환경 필수 |
CONFIG_CRYPTO_USER_API_SKCIPHER | AF_ALG skcipher 접근 | 사용자 공간 대칭 암호 |
CONFIG_CRYPTO_USER_API_HASH | AF_ALG hash 접근 | 사용자 공간 해시 |
CONFIG_CRYPTO_USER_API_AEAD | AF_ALG AEAD 접근 | 사용자 공간 AEAD |
CONFIG_CRYPTO_FIPS | FIPS 140 모드 활성화 | 자가 테스트 강제, 약한 알고리즘 비활성 |
CONFIG_CRYPTO_MANAGER | 템플릿 + 자가 테스트 관리자 | 알고리즘 조합·테스트에 필수 |
CONFIG_CRYPTO_AUTHENC | authenc 호환 AEAD | 레거시 IPsec ESP |
# ━━━ 커널 설정 확인 및 조정 ━━━
# 현재 커널의 Crypto 설정 확인
zcat /proc/config.gz | grep CONFIG_CRYPTO | head -30
# 또는 .config에서 확인
grep CONFIG_CRYPTO .config | grep -v '^#'
# 최소 필수 설정 (서버 용도)
# CONFIG_CRYPTO=y
# CONFIG_CRYPTO_AES=y (fallback)
# CONFIG_CRYPTO_AES_NI_INTEL=y (x86)
# CONFIG_CRYPTO_SHA256=y
# CONFIG_CRYPTO_GCM=y
# CONFIG_CRYPTO_HMAC=y
# CONFIG_CRYPTO_MANAGER=y
tcrypt 벤치마크와 자가 테스트
커널은 Crypto API 알고리즘의 정확성과 성능을 검증하기 위한 두 가지 내장 도구를 제공합니다:
자가 테스트 (testmgr)
알고리즘이 crypto_register_alg()으로 등록될 때, testmgr.c의 자가 테스트가 자동으로 실행됩니다. NIST 테스트 벡터 등 미리 정의된 입력/출력 쌍으로 구현의 정확성을 검증합니다:
# 자가 테스트 결과 확인 — /proc/crypto의 selftest 필드
grep -B2 -A1 "selftest" /proc/crypto | head -20
# name : cbc(aes)
# driver : cbc-aes-aesni
# selftest : passed ← 정상
# ---
# selftest : unknown ← 테스트 벡터 없음 (경고)
# FIPS 모드에서 자가 테스트 실패 시
# → 알고리즘 등록 자체가 거부됨 → 패닉 가능
# dmesg에 "alg: ... Test ... failed" 메시지 출력
tcrypt 벤치마크 모듈
tcrypt는 Crypto API 알고리즘의 처리량(throughput)과 지연(latency)을 측정하는 커널 모듈입니다. modprobe로 로드하면서 mode 파라미터로 벤치마크 유형을 지정합니다:
# ━━━ tcrypt 벤치마크 사용법 ━━━
# AES-CBC 속도 테스트 (mode=200: skcipher 속도 테스트)
modprobe tcrypt mode=200 sec=1
# → dmesg에 블록 크기별 처리량 출력
dmesg | tail -50
# test 1 (128 bit key, 16 byte blocks): ... MB/s
# test 1 (128 bit key, 1024 byte blocks): ... MB/s
# test 1 (128 bit key, 8192 byte blocks): ... MB/s
# SHA-256 해시 속도 테스트 (mode=300: hash 속도 테스트)
modprobe tcrypt mode=300 sec=1
# AES-GCM 속도 테스트 (mode=211)
modprobe tcrypt mode=211 sec=1
# 주요 mode 번호
# 200: AES-CBC 211: AES-GCM 215: ChaCha20-Poly1305
# 300: SHA-256 312: HMAC-SHA256
# 400: RSA 401: ECDH 402: ECDSA
# 모든 알고리즘 속도 테스트 (시간 소요 큼)
modprobe tcrypt mode=0 sec=1
# 테스트 완료 후 모듈 제거
rmmod tcrypt
tcrypt 활용 팁:
sec=5로 늘리면 결과의 분산이 줄어 더 안정적인 수치를 얻을 수 있습니다- HW 가속기 드라이버 로드 전후로 비교하면 가속 효과를 정량적으로 확인할 수 있습니다
/proc/crypto에서 선택된 드라이버를 확인한 뒤 tcrypt를 실행하세요 — 어떤 구현이 측정되는지 아는 것이 중요합니다- tcrypt는 로드 시 바로 실행되고, 완료 후
-EAGAIN으로modprobe가 실패한 것처럼 보이지만 정상입니다
주요 알고리즘 카탈로그
| 카테고리 | 알고리즘 | 커널 이름 | 용도 |
|---|---|---|---|
| 블록 암호 | AES-128/256 | aes | 디스크 암호화, IPsec |
| 스트림 암호 | ChaCha20 | chacha20 | WireGuard, TLS |
| 해시 | SHA-256 | sha256 | 무결성 검증 |
| 해시 | BLAKE2b | blake2b-256 | 고속 해싱 |
| MAC | HMAC-SHA256 | hmac(sha256) | 메시지 인증 |
| AEAD | AES-GCM | gcm(aes) | TLS, IPsec |
| AEAD | ChaCha20-Poly1305 | rfc7539(chacha20,poly1305) | WireGuard |
| KDF | HKDF | hkdf(hmac(sha256)) | 키 유도 |
| 압축 | LZ4, ZSTD | lz4, zstd | zswap, 파일시스템(Filesystem) |
사용자 공간(User Space) 인터페이스 (AF_ALG)
/* 사용자 공간에서 커널 Crypto Framework (Crypto API) 사용 */
int sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "hash",
.salg_name = "sha256",
};
bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));
int opfd = accept(sockfd, NULL, NULL);
write(opfd, data, data_len);
read(opfd, digest, 32); /* SHA-256 결과 */
AF_ALG 사용자 공간 인터페이스
AF_ALG은 사용자 공간에서 커널 Crypto API를 직접 사용할 수 있는 소켓(Socket) 인터페이스입니다. 별도의 암호 라이브러리 없이 커널에 등록된 모든 암호화 가속기(AES-NI, QAT 등)를 활용할 수 있으며, splice()를 통한 zero-copy 전송도 지원합니다.
AF_ALG 아키텍처
AF_ALG 소켓은 4단계 라이프사이클을 따릅니다:
/* AF_ALG 기본 패턴 */
int sockfd, connfd;
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "skcipher", /* "hash", "aead", "rng" */
.salg_name = "cbc(aes)", /* 알고리즘 이름 */
};
/* 1. 소켓 생성 + 알고리즘 바인드 */
sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));
/* 2. 키 설정 (skcipher/aead만) */
setsockopt(sockfd, SOL_ALG, ALG_SET_KEY, key, key_len);
/* 3. 연결 소켓 생성 (세션) */
connfd = accept(sockfd, NULL, 0);
/* 4. 데이터 송수신 (IV/AAD는 cmsg로 전달) */
sendmsg(connfd, &msg, 0); /* 평문 + cmsg(IV, op) */
read(connfd, out, out_len); /* 암호문 수신 */
close(connfd);
close(sockfd);
코드 설명
사용자 공간에서 커널 Crypto API에 접근하는 AF_ALG 소켓 인터페이스의 기본 4단계 패턴입니다 (crypto/af_alg.c, crypto/algif_skcipher.c 등).
- socket(AF_ALG, SOCK_SEQPACKET, 0)AF_ALG 패밀리의 순서 보장 소켓을 생성합니다.
SOCK_SEQPACKET은 메시지 경계를 보존하므로 암호 연산의 입출력 경계가 명확합니다. - bind() + sockaddr_alg
salg_type("skcipher","hash","aead","rng")과salg_name(알고리즘 정규 이름)을 지정하여 커널이 최적 우선순위의 알고리즘을 바인드합니다. - setsockopt(ALG_SET_KEY)대칭 암호 및 AEAD 알고리즘에서 키를 설정합니다. 이 호출은 커널 내부에서
crypto_skcipher_setkey()를 트리거합니다. - accept()연산 세션(operation socket)을 생성합니다. 각
accept()마다 독립된 암호 상태가 할당되므로, 멀티스레드 환경에서 별도 세션을 사용하면 안전합니다. - sendmsg() + cmsgIV, 연산 방향(
ALG_OP_ENCRYPT/ALG_OP_DECRYPT), AAD 길이 등을 제어 메시지(cmsg)로 전달하고, 데이터는 iovec으로 전송합니다.
해시 예제 (SHA-256)
#include <linux/if_alg.h>
#include <sys/socket.h>
int sha256_af_alg(const void *data, size_t len,
unsigned char digest[32])
{
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "hash",
.salg_name = "sha256",
};
int sockfd, connfd;
sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));
connfd = accept(sockfd, NULL, 0);
/* 데이터 전송 — 커널이 sha256-ni(또는 최적 드라이버)로 처리 */
write(connfd, data, len);
/* 다이제스트 수신 */
read(connfd, digest, 32);
close(connfd);
close(sockfd);
return 0;
}
/* splice() zero-copy: 파일 해시를 커널 공간에서 직접 처리 */
int sha256_file_splice(int fd, unsigned char digest[32])
{
/* ... socket/bind/accept 동일 ... */
int pipefd[2];
pipe(pipefd);
/* 파일 → 파이프 → AF_ALG: 사용자 공간 복사 없음 */
splice(fd, NULL, pipefd[1], NULL, file_size, 0);
splice(pipefd[0], NULL, connfd, NULL, file_size, 0);
read(connfd, digest, 32);
/* ... */
}
대칭 암호 예제 (AES-CBC)
int aes_cbc_encrypt_af_alg(const void *key, int keylen,
const void *iv, const void *pt, void *ct, int len)
{
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "skcipher",
.salg_name = "cbc(aes)",
};
int sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));
setsockopt(sockfd, SOL_ALG, ALG_SET_KEY, key, keylen);
int connfd = accept(sockfd, NULL, 0);
/* cmsg로 IV와 operation(encrypt/decrypt) 전달 */
struct msghdr msg = {};
struct cmsghdr *cmsg;
char cbuf[CMSG_SPACE(4) + CMSG_SPACE(16)];
struct iovec iov = { .iov_base = (void *)pt, .iov_len = len };
msg.msg_control = cbuf;
msg.msg_controllen = sizeof(cbuf);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
/* cmsg 1: operation = ALG_OP_ENCRYPT */
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_ALG;
cmsg->cmsg_type = ALG_SET_OP;
cmsg->cmsg_len = CMSG_LEN(4);
*(__u32 *)CMSG_DATA(cmsg) = ALG_OP_ENCRYPT;
/* cmsg 2: IV */
cmsg = CMSG_NXTHDR(&msg, cmsg);
cmsg->cmsg_level = SOL_ALG;
cmsg->cmsg_type = ALG_SET_IV;
cmsg->cmsg_len = CMSG_LEN(20); /* 4(ivlen) + 16(iv) */
struct af_alg_iv *aiv = (void *)CMSG_DATA(cmsg);
aiv->ivlen = 16;
memcpy(aiv->iv, iv, 16);
sendmsg(connfd, &msg, 0);
read(connfd, ct, len);
close(connfd);
close(sockfd);
return 0;
}
AEAD 예제 (AES-GCM)
/* AF_ALG AEAD: AES-256-GCM 암호화 */
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "aead",
.salg_name = "gcm(aes)",
};
int sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));
setsockopt(sockfd, SOL_ALG, ALG_SET_KEY, key, 32);
/* 인증 태그 크기 설정 (16바이트) */
setsockopt(sockfd, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, 16);
int connfd = accept(sockfd, NULL, 0);
/* cmsg에 ALG_SET_OP, ALG_SET_IV, ALG_SET_AEAD_ASSOCLEN 설정 */
/* sendmsg: [AAD (assoclen bytes)] + [plaintext] */
/* read: [AAD (assoclen bytes)] + [ciphertext] + [tag (16 bytes)] */
/* cmsg 3: AAD 길이 */
cmsg->cmsg_type = ALG_SET_AEAD_ASSOCLEN;
cmsg->cmsg_len = CMSG_LEN(4);
*(__u32 *)CMSG_DATA(cmsg) = aad_len;
/* iov[0] = AAD, iov[1] = plaintext */
sendmsg(connfd, &msg, 0);
/* 수신: AAD + ciphertext + 16바이트 GCM 태그 */
read(connfd, outbuf, aad_len + pt_len + 16);
AF_ALG 성능 비교
| 작업 (8KB 블록) | AF_ALG | OpenSSL (EVP) | 비고 |
|---|---|---|---|
| AES-256-CBC 암호화 | ~1.2 GB/s | ~1.5 GB/s | AF_ALG 소켓 오버헤드 존재 |
| SHA-256 해시 | ~1.8 GB/s | ~2.0 GB/s | 커널 전환 비용 |
| SHA-256 (splice) | ~2.1 GB/s | — | zero-copy로 역전 가능 |
| AES-GCM | ~1.0 GB/s | ~1.3 GB/s | AEAD cmsg 오버헤드 |
AF_ALG vs OpenSSL: 소량 데이터에서는 소켓/커널 전환 오버헤드로 AF_ALG가 느리지만, splice() zero-copy나 QAT 등 전용 가속기를 활용할 때는 AF_ALG가 유리할 수 있습니다. AF_ALG의 주요 가치는 (1) 라이브러리 의존성 없는 커널 crypto 접근, (2) FIPS 모드 커널 모듈 직접 활용, (3) QAT/CCP 등 커널 전용 가속기 사용입니다.
/proc/crypto와 드라이버 선택
# ━━━ /proc/crypto 활용법 ━━━
# 특정 알고리즘의 모든 구현 확인
awk '/^name.*cbc\(aes\)/,/^$/' /proc/crypto
# name : cbc(aes)
# driver : cbc-aes-aesni priority: 400
# name : cbc(aes)
# driver : cbc-aes-generic priority: 100
# AF_ALG에서 특정 드라이버 강제 지정
# salg_name에 드라이버 이름을 직접 사용
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "skcipher",
.salg_name = "cbc-aes-aesni", /* 드라이버 이름 직접 지정 */
};
# 전체 알고리즘 유형별 개수
grep '^type' /proc/crypto | sort | uniq -c | sort -rn
# 45 type : skcipher
# 32 type : shash
# 18 type : aead
# 8 type : ahash
# 5 type : rng
보안 주의: AF_ALG 소켓은 CAP_NET_ADMIN 없이도 사용할 수 있으므로, 일반 사용자가 커널 crypto를 호출할 수 있습니다. 커널 5.9+ 이후 crypto.fips_enabled=1 부트 파라미터가 설정되면 AF_ALG도 FIPS 인증 알고리즘만 사용 가능합니다.
구현 시 자주 틀리는 지점
알고리즘 선택은 맞는데 실제 동작이 깨지는 경우, 아래 항목 중 하나에 걸린 경우가 많습니다. 이 표는 Crypto API를 소비하는 코드 관점의 체크리스트입니다. 앞서 나온 "알고리즘 구현 체크리스트"와는 성격이 다릅니다.
| 실수 | 왜 문제인가 | 수정 방향 |
|---|---|---|
| 패킷마다 tfm alloc/free | 키 스케줄, module ref, priority lookup 오버헤드가 누적됩니다. | 연결, inode, queue, state 객체에 tfm을 매달아 장기간 재사용합니다. |
| 한 request를 동시 재사용 | callback/data/reqctx가 뒤섞여 UAF 또는 데이터 오염이 납니다. | in-flight 요청 수만큼 request를 분리하거나 per-CPU 풀을 둡니다. |
atomic 문맥에서 crypto_wait_req() 사용 | 잠들 수 없는 경로에서 completion 대기를 걸어 deadlock 또는 경고가 납니다. | atomic 경로는 진짜 비동기 콜백으로 처리하거나 cryptd 전용 경로를 씁니다. |
| AEAD tag 공간 미확보 | 암호문 뒤 tag를 쓸 곳이 없어 메모리 파손이 납니다. | 암호화 출력 버퍼는 항상 plaintext + authsize 이상 확보합니다. |
AEAD에서 assoclen/cryptlen 혼동 | 태그 검증 실패가 나도 겉보기엔 키나 IV 문제처럼 보입니다. | 복호화에서는 cryptlen = ciphertext_len + authsize임을 기억합니다. |
| SG 조각을 억지로 memcpy해서 연속 버퍼화 | 캐시 낭비와 추가 복사가 생기고 DMA offload 이점이 줄어듭니다. | ahash, skcipher, aead의 SG 인터페이스를 그대로 활용합니다. |
키/IV/PRK를 일반 kfree()만 하고 끝냄 | 민감 값이 메모리에 남아 추후 관찰될 수 있습니다. | memzero_explicit(), kfree_sensitive() 계열로 정리합니다. |
| 드라이버 이름을 하드코딩해 강제 바인딩 | 특정 CPU/보드에서만 동작하고 fallback이 깨질 수 있습니다. | 가급적 정규 이름(gcm(aes), sha256)을 사용하고, 디버깅 때만 드라이버 이름을 강제합니다. |
| fallback 경로 미검증 | HW 없는 장비나 softirq 경로에서만 터지는 버그가 숨어 있습니다. | generic 구현, cryptd 경로, HW 가속 경로를 각각 나눠 테스트합니다. |
/proc/crypto를 안 보고 체감으로만 판단 | 실제로 어떤 구현이 선택됐는지 모르고 성능/오류를 추정하게 됩니다. | name, driver, priority, async, selftest를 항상 같이 확인합니다. |
관련 문서
Crypto Framework (Crypto API)와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.
외부 참고 자료
- Kernel Crypto API — Documentation — 커널 암호화 프레임워크 공식 문서입니다
- Crypto API Introduction — Crypto API 개요 및 아키텍처 소개입니다
- Crypto API Architecture — Crypto API 내부 아키텍처 상세 문서입니다
- Developing Cipher Algorithms — 커널 암호 알고리즘 개발 가이드입니다
- Linux 커널 crypto/ 디렉토리 — 커널 암호화 서브시스템 소스 코드입니다
- x86 Crypto 가속 구현 — AES-NI, AVX2, SHA-NI 등 x86 하드웨어 가속 소스입니다
- AF_ALG — Linux man page — 사용자 공간에서 커널 Crypto API에 접근하는 소켓 인터페이스입니다
- Intel AES-NI — AES-NI 명령어 세트 기술 문서입니다
- NIST CAVP — 암호 알고리즘 검증 프로그램 (FIPS 인증 테스트)입니다
- LWN.net — Cryptography — LWN의 커널 암호화 관련 기사 인덱스입니다