WinZip AE-1 と AE-2 は、.zip アーカイブ内部で使用される AES 暗号化方式で、脆弱だった従来の PKZIP 2.0 ストリーム暗号を置き換えるものです。すべての関数はsgcCrypto_Zip_AE2にあり、アーカイブのエントリを1つずつ処理します。周囲の .zip コンテナそのものを構築することは、このユニットの範囲外です。
レイアウト
鍵材料は、パスワードに対して PBKDF2-HMAC-SHA1 を 1000 回繰り返すことで得られ、暗号化鍵、認証鍵、そして 2 バイトのパスワード検証子を1回の呼び出しで生成します。ソルトの長さと鍵の長さは、いずれもエントリに選択された AES の強度によって決まります。
function sgcZipAE2_SaltLength(aStrength: TsgcZipAESStrength): Integer;
function sgcZipAE2_KeyLength(aStrength: TsgcZipAESStrength): Integer;
procedure sgcZipAE2_DeriveKeys(const aPassword, aSalt: TBytes; aStrength: TsgcZipAESStrength;
out aEncKey, aAuthKey, aVerifier: TBytes);
エントリの暗号化と復号
function sgcZipAE2_Encrypt(const aPassword, aSalt, aPlain: TBytes; aStrength: TsgcZipAESStrength;
out aVerifier, aAuthCode: TBytes): TBytes;
function sgcZipAE2_Decrypt(const aPassword, aSalt, aCipher, aVerifier, aAuthCode: TBytes;
aStrength: TsgcZipAESStrength; out aPlain: TBytes): Boolean;
var
oSalt, oCipher, oVerifier, oAuth, oPlain: TBytes;
begin
oSalt := sgcRandomBytes(sgcZipAE2_SaltLength(zasAES256));
oCipher := sgcZipAE2_Encrypt(oPassword, oSalt, oPlain, zasAES256, oVerifier, oAuth);
if sgcZipAE2_Decrypt(oPassword, oSalt, oCipher, oVerifier, oAuth, zasAES256, oPlain) then
{ correct password, untampered data };
end;
2 バイトの検証子はほとんどの誤ったパスワードをその場で検出し、aAuthCodeが保持する 10 バイトの HMAC-SHA1 認証コードが残りを検出するとともに、暗号化されたコンテンツへの改ざんも検出します。
AE-2 が保護しないもの
認証コードがカバーするのは、1エントリのファイル内容だけです。ファイル名、タイムスタンプ、アーカイブのレイアウトはカバーされないため、内容を読めない攻撃者であっても、検出されることなくアーカイブ内のエントリの名前を変更したり、並べ替えたり、削除したりできます。また、弱いパスワードに対しても保護しません。鍵導出はわずか 1000 回の PBKDF2 反復しか使用しておらず、この形式がそれらより前に策定されたため、Argon2id や scryptが使用する回数よりはるかに少なくなっています。AE-2 アーカイブの強度は、そのために選ばれたパスワードの強度でしかありません。
エクストラフィールド
.zip エントリは、圧縮方式を 99 に設定し、小さなエクストラフィールド(ヘッダー ID 0x9901)を追加することで、自身が AE 暗号化されていることを示します。このフィールドには、内部で実際に使われている圧縮方式、AE のバージョン(1 または 2)、そして AES の強度が記録されており、リーダーがパスワードを試みる前に必要とする情報です。
function sgcZipAE2_BuildExtraField(aStrength: TsgcZipAESStrength; aCompressionMethod: Word;
aVersion: TsgcZipAEVersion = zavAE2): TBytes;
function sgcZipAE2_ParseExtraField(const aData: TBytes; out aStrength: TsgcZipAESStrength;
out aCompressionMethod: Word; out aVersion: TsgcZipAEVersion): Boolean;
ローカルファイルヘッダーに続く、ソルト / 検証子 / 暗号文 / 認証コードというレイアウトを組み立て、または読み取るための便利な関数が2つあり、暗号処理だけでなくエントリ全体を組み立てたり読み取ったりする呼び出し元向けに用意されています。
function sgcZipAE2_PackEntry(const aSalt, aVerifier, aCipher, aAuthCode: TBytes): TBytes;
function sgcZipAE2_UnpackEntry(const aEntry: TBytes; aStrength: TsgcZipAESStrength;
out aSalt, aVerifier, aCipher, aAuthCode: TBytes): Boolean;