외부 업체와 데이터를 주고받다 보면 "AES-256-ECB로 암호화해서 보내 달라"는 연동 규격을 꽤 자주 만납니다. PHP에서는 OpenSSL 확장이 기본으로 들어 있어서, 별도 라이브러리 없이 openssl_encrypt / openssl_decrypt 두 함수로 처리할 수 있습니다. 실제로 쓰던 함수를 키 값만 가려서 올립니다.
<?
$iv = 'YOUR_IV_16_CHARS';
$key = "YOUR_KEY_32_CHARS";
function encrypt($original_string, $crypt_key, $crypt_iv) {
$option = 0;
$cipher_algo = "AES-256-ECB";
return openssl_encrypt($original_string, $cipher_algo, $crypt_key, $option, $crypt_iv);
}
function decrypt($encrypted_string, $crypt_key, $crypt_iv) {
$option = 0;
$cipher_algo = "AES-256-ECB";
$decrypted_string = openssl_decrypt ($encrypted_string, $cipher_algo, $crypt_key, $option, $crypt_iv);
return substr($decrypted_string,16);
}
?>
옵션 0의 의미
$option = 0 이면 암호화 결과가 Base64 문자열로 나옵니다. 그래서 반환값을 그대로 URL 파라미터나 JSON에 넣기 편하고, 복호화할 때도 Base64 문자열을 그대로 넘기면 됩니다. 바이너리 원본이 필요하면 OPENSSL_RAW_DATA 를 주는데, 이 경우 DB나 HTTP로 보낼 때 직접 인코딩해야 합니다. 상대 시스템이 Base64를 기대하는지, 16진수 문자열을 기대하는지는 연동 규격에서 꼭 확인해야 할 부분입니다. 여기서 어긋나면 키가 맞아도 복호화가 실패합니다.
AES-256은 키 길이가 32바이트입니다. $key 를 32자 문자열로 둔 이유가 그겁니다. 키가 짧으면 OpenSSL이 뒤를 0으로 채워서 처리하기 때문에 오류 없이 엉뚱한 결과가 나오니, 길이부터 맞춰 두세요.
ECB 모드와 IV
ECB는 블록마다 독립적으로 암호화하는 방식이라 IV를 쓰지 않습니다. 위 코드는 IV를 넘기고 있지만 결과에는 영향이 없고, PHP 버전에 따라 "IV 길이가 맞지 않는다"는 경고가 로그에 남을 수 있습니다. 경고가 거슬리면 ECB에서는 다섯 번째 인자를 빈 문자열로 넘기면 됩니다. 반대로 CBC 같은 모드로 바꾸면 그때부터는 IV가 필수이고 16바이트여야 합니다.
복호화 결과에서 앞 16자를 버리는 이유
decrypt 함수는 복호화한 문자열에서 substr($decrypted_string,16) 으로 앞 16자를 잘라 내고 돌려줍니다. 이건 연동 상대가 평문 앞에 16바이트짜리 값을 붙여서 암호화해 보내는 규격에 맞춘 처리로 보입니다. 주의할 점은 이 파일의 encrypt 는 아무것도 붙이지 않는다는 겁니다. 그래서 이 두 함수로 직접 암호화했다가 복호화하면 원문 앞 16자가 사라집니다. 자기 시스템 안에서 왕복으로 쓸 거라면 substr 없이 결과를 그대로 돌려주도록 바꿔야 합니다.
ECB는 같은 평문 블록이 항상 같은 암호문이 되기 때문에, 데이터 패턴이 드러날 수 있어 새로 설계하는 시스템에는 권하지 않습니다. 상대가 정한 규격이라 어쩔 수 없을 때만 쓰고, 직접 정할 수 있다면 AES-256-GCM 이나 무작위 IV를 쓰는 AES-256-CBC 를 고르는 게 맞습니다. 그리고 키를 소스 파일에 박아 두기보다 .env 같은 설정 파일로 빼 두면 소스를 공유할 때 키가 같이 새어 나가는 일을 막을 수 있습니다. 끝으로 <? 짧은 태그는 서버 설정(short_open_tag)에 따라 동작하지 않을 수 있으니, 옮겨 쓸 때는 <?php 로 바꾸는 편이 안전합니다.