665 lines
18 KiB
Plaintext
665 lines
18 KiB
Plaintext
unit Tocsg.COLib.Encrypt;
|
|
|
|
interface
|
|
|
|
uses
|
|
Winapi.Windows, System.SysUtils, System.Classes, Vcl.Dialogs, System.Math, System.IOUtils,
|
|
Tocsg.COLib, Tocsg.Safe;
|
|
|
|
const
|
|
SIG_CO_DRM = 'ToCSG_CO';
|
|
DRM_CO_VER = 1;
|
|
MAX_HEAD_STR = 260;
|
|
|
|
type
|
|
// 헤더 구조체 (Packed로 선언하여 메모리 정렬 고정)
|
|
TTgDrmHead = packed record
|
|
wVer: WORD;
|
|
dwCustomerCode: DWORD;
|
|
sEmpNo,
|
|
sHostName,
|
|
sPartName,
|
|
sPgName: array [0..MAX_HEAD_STR - 1] of UTF8Char;
|
|
dtEnc: TDateTime;
|
|
end;
|
|
|
|
TTgColibDrm = class
|
|
private
|
|
class function EncryptKEK(const pKek: TBytes; const sFilePath: string): Boolean;
|
|
class function DecryptKEK(const sPath: string): TBytes;
|
|
class procedure EncryptDEKtoKEK(const pDek, pKek: TBytes);
|
|
class function DecryptDEKtoKEK(sPath:string; pKek:TBytes): TBytes;
|
|
class function CheckSig(aStream: TStream): Boolean;
|
|
// 헤더 정보를 읽고 복호화하여 스트림 위치를 데이터 시작점으로 이동
|
|
class function ExtractHead(aSrcStream: TStream; pDek: TBytes; var aHead: TTgDrmHead): Boolean;
|
|
public
|
|
class function CreateSalt(const iKeylen: Integer): TBytes;
|
|
class function CreateKEK(const sPass, sSalt: AnsiString; Iterations, iKeyLen: Integer): TBytes;
|
|
class procedure CreateDEK(const iKeylen: Integer; pKek: TBytes);
|
|
class function GetKEK(sPath:string ): TBytes;
|
|
class function GetDEK(sPath:string; pKek:TBytes): TBytes;
|
|
// 헤더 정보를 생성하고 파일 스트림에 기록
|
|
//class function SetHead(aDstStream: TStream; pDek: TBytes; sEmpNo, sHostName, sPartName, sPgName: UTF8String): Boolean;
|
|
//class procedure DoEncrypt(sPath: string; pDek: TBytes; sEmpNo, sHostName, sPartName, sPgName: UTF8String);
|
|
class function SetHead(aDstStream: TStream; pDek: TBytes): Boolean;
|
|
class function DoEncrypt(sPath: string; pDek: TBytes): Boolean;
|
|
class procedure DoDecrypt(sPath: string; pDek: TBytes);
|
|
class function IsEncrypt(sPath: string): Boolean;
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TTgColibDrm }
|
|
|
|
type
|
|
TDataBlob = record
|
|
cbData: DWORD;
|
|
pbData: PByte;
|
|
end;
|
|
PDataBlob = ^TDataBlob;
|
|
|
|
function CryptProtectData(pDataIn: PDataBlob; szDataDescr: PWideChar;
|
|
pOptionalEntropy: PDataBlob; pvReserved: Pointer; pPromptStruct: Pointer;
|
|
dwFlags: DWORD; pDataOut: PDataBlob): BOOL; stdcall;
|
|
external 'crypt32.dll' name 'CryptProtectData';
|
|
|
|
function CryptUnprotectData(pDataIn: PDataBlob; ppszDataDescr: PPWideChar;
|
|
pOptionalEntropy: PDataBlob; pvReserved: Pointer; pPromptStruct: Pointer;
|
|
dwFlags: DWORD; pDataOut: PDataBlob): BOOL; stdcall;
|
|
external 'crypt32.dll' name 'CryptUnprotectData';
|
|
|
|
function ConcatBytes(const pBuf1, pBuf2: TBytes): TBytes;
|
|
begin
|
|
SetLength(Result, Length(pBuf1) + Length(pBuf2));
|
|
Move(pBuf1[0], Result[0], Length(pBuf1));
|
|
Move(pBuf2[0], Result[Length(pBuf1)], Length(pBuf2));
|
|
end;
|
|
|
|
procedure XORBytes(var pBuf1: TBytes; const pBuf2: TBytes);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to Min(Length(pBuf1), Length(pBuf2)) - 1 do
|
|
pBuf1[i] := pBuf1[i] xor pBuf2[i];
|
|
end;
|
|
|
|
class function TTgColibDrm.CreateSalt(const iKeylen: Integer): TBytes;
|
|
var
|
|
nResult: Integer;
|
|
pSalt: TBytes;
|
|
sPath: string;
|
|
begin
|
|
sPath := 'salt.bin';
|
|
|
|
if TFile.Exists(sPath) then
|
|
begin
|
|
Result := TFile.ReadAllBytes(sPath);
|
|
Exit;
|
|
end;
|
|
|
|
// 파일이 없는 경우: 새로운 Salt 생성
|
|
SetLength(pSalt, iKeylen);
|
|
|
|
// SHA-256 난수
|
|
nResult := COLibDrbg(@pSalt[0], Length(pSalt), nil, 0, nil, 0, 0);
|
|
if nResult = COLIB_SUCCESS then
|
|
begin
|
|
// 생성 성공 시 파일로 기록
|
|
try
|
|
TFile.WriteAllBytes(sPath, pSalt);
|
|
Result := pSalt;
|
|
except
|
|
on E: Exception do
|
|
begin
|
|
ShowMessage('Salt 파일 저장 중 오류: ' + E.Message);
|
|
Result := nil;
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
// 생성 실패 시 처리
|
|
ShowMessage('Salt 생성 실패: ' + IntToStr(nResult) + ', ' + COLibGetErrorStr(nResult));
|
|
Result := nil;
|
|
end;
|
|
end;
|
|
|
|
class function TTgColibDrm.EncryptKEK(const pKek: TBytes; const sFilePath: string): Boolean;
|
|
var
|
|
DataIn, DataOut: TDataBlob;
|
|
FileStream: TFileStream;
|
|
begin
|
|
Result := False;
|
|
|
|
if Length(pKek) = 0 then
|
|
begin
|
|
ShowMessage('KEK가 비어있습니다.');
|
|
Exit;
|
|
end;
|
|
|
|
// TBytes 데이터를 DataIn 구조체에 연결
|
|
DataIn.pbData := @pKek[0];
|
|
DataIn.cbData := Length(pKek);
|
|
|
|
// DPAPI 암호화 호출 (현재 사용자 계정으로만 복호화 가능하도록 설정)
|
|
// 세 번째 인자는 엔트로피(추가 암호)
|
|
if CryptProtectData(@DataIn, nil, nil, nil, nil, 0, @DataOut) then
|
|
begin
|
|
try
|
|
FileStream := TFileStream.Create(sFilePath, fmCreate);
|
|
try
|
|
FileStream.WriteBuffer(DataOut.pbData^, DataOut.cbData);
|
|
Result := True;
|
|
finally
|
|
FileStream.Free;
|
|
end;
|
|
finally
|
|
// DPAPI에서 할당한 메모리 해제
|
|
LocalFree(HLOCAL(DataOut.pbData));
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
ShowMessage('CryptProtectData is failed: ' + IntToStr(GetLastError));
|
|
end;
|
|
end;
|
|
|
|
class function TTgColibDrm.DecryptKEK(const sPath: string): TBytes;
|
|
var
|
|
DataIn, DataOut: TDataBlob;
|
|
ms: TMemoryStream;
|
|
begin
|
|
SetLength(Result, 0);
|
|
|
|
if not FileExists(sPath) then
|
|
begin
|
|
ShowMessage('KEK가 존재하지 않습니다');
|
|
Exit;
|
|
end;
|
|
|
|
ms := TMemoryStream.Create;
|
|
try
|
|
ms.LoadFromFile(sPath);
|
|
DataIn.pbData := ms.Memory;
|
|
DataIn.cbData := ms.Size;
|
|
|
|
// DPAPI 복호화 호출
|
|
if CryptUnprotectData(@DataIn, nil, nil, nil, nil, 0, @DataOut) then
|
|
begin
|
|
try
|
|
// 복호화된 바이너리 데이터를 TBytes로 복사
|
|
SetLength(Result, DataOut.cbData);
|
|
if DataOut.cbData > 0 then
|
|
Move(DataOut.pbData^, Result[0], DataOut.cbData);
|
|
finally
|
|
LocalFree(HLOCAL(DataOut.pbData));
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
ShowMessage('CryptUnprotectData is failed: ' + IntToStr(GetLastError));
|
|
end;
|
|
finally
|
|
ms.Free;
|
|
end;
|
|
end;
|
|
|
|
class function TTgColibDrm.CreateKEK(const sPass, sSalt: AnsiString; Iterations, iKeyLen: Integer): TBytes;
|
|
var
|
|
i, n, nBlockCnt: Integer;
|
|
pSaltAndBlkIdx, pBlockIdx, pTemp, U_prev, U_current, pPass, pSalt: TBytes;
|
|
ci: TCIPHER_INFO;
|
|
nOut, nResult: Integer;
|
|
path: string;
|
|
begin
|
|
path := 'kek.bin';
|
|
|
|
if TFile.Exists(path) then
|
|
begin
|
|
// 파일이 존재하면 복호화후 리턴
|
|
//ShowMessage('실제 감지된 경로: ' + TPath.GetFullPath(path));
|
|
Result := DecryptKEK(path);
|
|
Exit;
|
|
end;
|
|
|
|
SetLength(Result, iKeyLen);
|
|
ZeroMemory(Result, iKeyLen);
|
|
|
|
if (sPass = '') or (sSalt = '') then
|
|
exit;
|
|
|
|
n := Length(sPass);
|
|
if n < 16 then
|
|
begin
|
|
SetLength(pPass, 16);
|
|
ZeroMemory(pPass, 16);
|
|
end else
|
|
SetLength(pPass, n);
|
|
CopyMemory(pPass, @sPass[1], n);
|
|
|
|
n := Length(sSalt);
|
|
SetLength(pSalt, n);
|
|
CopyMemory(pSalt, @sSalt[1], n);
|
|
|
|
ZeroMemory(@ci, SizeOf(ci));
|
|
|
|
SetLength(pBlockIdx, 4);
|
|
SetLength(pTemp, iKeyLen);
|
|
SetLength(U_prev, iKeyLen);
|
|
SetLength(U_current, iKeyLen);
|
|
|
|
// PBKDF
|
|
// 32바이트 단위(SHA256)의 각 블록마다 다음을 수행
|
|
// 1) U₁ = HMAC(Pass, Salt || BlockIndex)
|
|
// 2) U_n = HMAC(Pass, U_{n-1})
|
|
// 3) U_{n-1} XOR U_n
|
|
|
|
// 32 바이트씩 나눠서 블록개수 계산
|
|
nBlockCnt := (iKeyLen + 31) div iKeyLen; // SHA256 = 32 bytes
|
|
for i := 1 to nBlockCnt do
|
|
begin
|
|
// BlockIndex = i (Big Endian)
|
|
// 블록 번호를 Big Endian으로 표현한 4바이트 ex) i=[0,0,0,1]
|
|
// int(i)로 표현
|
|
pBlockIdx[0] := Byte((i shr 24) and $FF);
|
|
pBlockIdx[1] := Byte((i shr 16) and $FF);
|
|
pBlockIdx[2] := Byte((i shr 8) and $FF);
|
|
pBlockIdx[3] := Byte(i and $FF);
|
|
|
|
// Salt || int(i)
|
|
pSaltAndBlkIdx := ConcatBytes(pSalt, pBlockIdx);
|
|
|
|
nOut := 0;
|
|
// 1) U₁ = HMAC(Pass, Salt || BlockIndex)
|
|
COLibSetCipherInfo(@ci, COLIB_MODE_HMAC, BLOCK_CRYPTO_MODE_UNUSED, COLIB_PAD_TYPE_UNUSED,
|
|
@pPass[0], Length(pPass), nil, 0, @pSaltAndBlkIdx[0], Length(pSaltAndBlkIdx), @U_prev[0], @nOut);
|
|
nResult := COLibEncrypt(@ci);
|
|
if nResult <> COLIB_SUCCESS then
|
|
exit;
|
|
|
|
CopyMemory(pTemp, U_prev, Length(U_prev));
|
|
|
|
for n := 2 to Iterations do
|
|
begin
|
|
nOut := 0;
|
|
// 2) U_n = HMAC(Pass, U_{n-1})
|
|
COLibSetCipherInfo(@ci, COLIB_MODE_HMAC, BLOCK_CRYPTO_MODE_UNUSED, COLIB_PAD_TYPE_UNUSED,
|
|
@pPass[0], Length(pPass), nil, 0, @U_prev[0], Length(U_prev), @U_current[0], @nOut);
|
|
nResult := COLibEncrypt(@ci);
|
|
if nResult <> COLIB_SUCCESS then
|
|
exit;
|
|
|
|
// 3) U_{n-1} XOR U_n
|
|
XORBytes(pTemp, U_current);
|
|
CopyMemory(U_prev, U_current, Length(U_current));
|
|
end;
|
|
|
|
Move(pTemp[0], Result[(i - 1) * iKeyLen], Min(iKeyLen, iKeyLen - (i - 1) * iKeyLen));
|
|
end;
|
|
|
|
if not (EncryptKEK(Result, path)) then
|
|
begin
|
|
ZeroMemory(Result, iKeyLen);
|
|
ShowMessage('EncryptKEK is failed');
|
|
end;
|
|
end;
|
|
|
|
class function TTgColibDrm.GetKEK(sPath:string): TBytes;
|
|
begin
|
|
if TFile.Exists(sPath) then
|
|
begin
|
|
Result := DecryptKEK(sPath);
|
|
end
|
|
else
|
|
begin
|
|
//ShowMessage('KEK가 존재하지 않습니다');
|
|
Result := nil;
|
|
end;
|
|
end;
|
|
|
|
class function TTgColibDrm.GetDEK(sPath:string; pKek:TBytes): TBytes;
|
|
begin
|
|
if TFile.Exists(sPath) then
|
|
begin
|
|
Result := DecryptDEKtoKEK(sPath, pKek);
|
|
end
|
|
else
|
|
begin
|
|
//ShowMessage('DEK가 존재하지 않습니다.');
|
|
Result := nil;
|
|
end;
|
|
end;
|
|
|
|
class procedure TTgColibDrm.EncryptDEKtoKEK(const pDek, pKek: TBytes);
|
|
var
|
|
nResult, nOut: Integer;
|
|
ci: TCIPHER_INFO;
|
|
arrIV: array [0..COLIB_BLOCK_LEN-1] of Byte;
|
|
arrEnc: TBytes;
|
|
sOut, sIn: string;
|
|
begin
|
|
ZeroMemory(@ci, SizeOf(ci));
|
|
ZeroMemory(@arrIV, SizeOf(arrIV));
|
|
|
|
SetLength(arrEnc, Length(pDek)*2);
|
|
|
|
// for var i := 0 to Length(pDek) - 1 do
|
|
// sIn := sIn + Format('%.2x', [pDek[i]]);
|
|
// ShowMessage('평문 DEK (Hex) : ' + Inttostr(Length(pDek))+', '+sIn);
|
|
|
|
COLibSetCipherInfo(@ci, COLIB_MODE_ARIA, BLOCK_CRYPTO_MODE_CBC, COLIB_PAD_TYPE_NO,
|
|
@pKek[0], Length(pKek), @arrIV, SizeOf(arrIV),
|
|
@pDek[0], Length(pDek), @arrEnc[0], @nOut);
|
|
|
|
nResult := COLibEncrypt(@ci);
|
|
if nResult <> COLIB_SUCCESS then
|
|
begin
|
|
ShowMessage('COLibEncrypt is failed: ' + IntToStr(nResult) + ', ' + COLibGetErrorStr(nResult));
|
|
Exit;
|
|
end;
|
|
|
|
// for var i := 0 to nOut - 1 do
|
|
// sOut := sOut + Format('%.2x', [arrEnc[i]]);
|
|
// ShowMessage('암호화 DEK (Hex) : ' + Inttostr(nOut)+', '+sOut);
|
|
|
|
// 암호화된 DEK를 바이너리 그대로 파일에 저장
|
|
TFile.WriteAllBytes('dek.bin', Copy(arrEnc, 0, nOut));
|
|
end;
|
|
|
|
class procedure TTgColibDrm.CreateDEK(const iKeylen: Integer; pKek: TBytes);
|
|
var
|
|
pDek: TBytes;
|
|
sPath: string;
|
|
begin
|
|
sPath := 'dek.bin';
|
|
|
|
if TFile.Exists(sPath) then
|
|
begin
|
|
Exit;
|
|
end;
|
|
|
|
SetLength(pDek, iKeylen);
|
|
COLibGetKey(@pDek[0], iKeylen);
|
|
|
|
EncryptDEKtoKEK(pDek, pKek);
|
|
end;
|
|
|
|
class function TTgColibDrm.DecryptDEKtoKEK(sPath:string; pKek:TBytes): TBytes;
|
|
var
|
|
nResult, nOut: Integer;
|
|
ci: TCIPHER_INFO;
|
|
arrIV: array [0..COLIB_BLOCK_LEN-1] of Byte;
|
|
arrDec, encDek: TBytes;
|
|
sOut, sIn: string;
|
|
begin
|
|
ZeroMemory(@ci, SizeOf(ci));
|
|
ZeroMemory(@arrIV, SizeOf(arrIV));
|
|
|
|
// if not TFile.Exists(sPath) then
|
|
// begin
|
|
// ShowMessage('DEK 존재하지 않음');
|
|
// Result := nil;
|
|
// Exit;
|
|
// end;
|
|
|
|
encDek := TFile.ReadAllBytes(sPath);
|
|
|
|
SetLength(arrDec, Length(encDek)); // 암호문 길이만큼 버퍼 확보
|
|
|
|
// for var i := 0 to Length(encDek) - 1 do
|
|
// sIn := sIn + Format('%.2x', [encDek[i]]);
|
|
// ShowMessage('암호화된 DEK (Hex) : ' + Inttostr(Length(encDek))+', '+sIn);
|
|
|
|
COLibSetCipherInfo(@ci, COLIB_MODE_ARIA, BLOCK_CRYPTO_MODE_CBC, COLIB_PAD_TYPE_NO,
|
|
@pKek[0], Length(pKek), @arrIV, SizeOf(arrIV),
|
|
@encDek[0], Length(encDek), @arrDec[0], @nOut);
|
|
|
|
nResult := COLibDecrypt(@ci);
|
|
if nResult <> COLIB_SUCCESS then
|
|
begin
|
|
ShowMessage('COLibDecrypt is failed: ' + IntToStr(nResult) + ', ' + COLibGetErrorStr(nResult));
|
|
Result := nil;
|
|
Exit;
|
|
end;
|
|
|
|
// 실제 복호화된 길이만큼 잘라내기
|
|
//SetLength(arrDec, nOut);
|
|
|
|
// for var i := 0 to nOut - 1 do
|
|
// sOut := sOut + Format('%.2x', [arrDec[i]]);
|
|
// ShowMessage('복호화 DEK (Hex) : ' + Inttostr(Length(arrDec))+', '+sOut);
|
|
|
|
Result := arrDec;
|
|
end;
|
|
|
|
class function TTgColibDrm.CheckSig(aStream: TStream): Boolean;
|
|
var
|
|
sSig: AnsiString;
|
|
pBuf: array[0..7] of AnsiChar; // SIG_CO_DRM 길이만큼
|
|
begin
|
|
Result := False;
|
|
if aStream.Size < Length(SIG_CO_DRM) then Exit;
|
|
|
|
aStream.Position := 0;
|
|
aStream.Read(pBuf, Length(SIG_CO_DRM));
|
|
sSig := Copy(pBuf, 0, Length(SIG_CO_DRM));
|
|
|
|
Result := (sSig = SIG_CO_DRM);
|
|
end;
|
|
|
|
class function TTgColibDrm.IsEncrypt(sPath: string): Boolean;
|
|
var
|
|
fs: TFileStream;
|
|
begin
|
|
Result := False;
|
|
if not FileExists(sPath) then Exit;
|
|
|
|
try
|
|
Guard(fs, TFileStream.Create(sPath, fmOpenRead or fmShareDenyNone));
|
|
Result := CheckSig(fs);
|
|
except
|
|
Result := False;
|
|
end;
|
|
end;
|
|
|
|
class function TTgColibDrm.SetHead(aDstStream: TStream; pDek: TBytes): Boolean;
|
|
var
|
|
Head: TTgDrmHead;
|
|
ci: TCIPHER_INFO;
|
|
arrIV: array [0..COLIB_BLOCK_LEN-1] of Byte;
|
|
pEncHead: TBytes;
|
|
nOut, nResult: Integer;
|
|
begin
|
|
Result := False;
|
|
|
|
ZeroMemory(@Head, SizeOf(Head));
|
|
Head.wVer := DRM_CO_VER;
|
|
Head.dwCustomerCode := 13001;
|
|
Head.dtEnc := Now;
|
|
//Head.llEncDT := DelphiDtToJavaDt(Now);
|
|
StrCopy(Head.sPgName, PUTF8Char(SIG_CO_DRM));
|
|
StrCopy(Head.sEmpNo, PUTF8Char(SIG_CO_DRM));
|
|
StrCopy(Head.sPartName, PUTF8Char(SIG_CO_DRM));
|
|
StrCopy(Head.sHostName, PUTF8Char(SIG_CO_DRM));
|
|
// Move(PAnsiChar(sEmpNo)^, Head.sEmpNo[0], Min(Length(sEmpNo), MAX_HEAD_STR));
|
|
// Move(PAnsiChar(sHostName)^, Head.sHostName[0], Min(Length(sHostName), MAX_HEAD_STR));
|
|
// Move(PAnsiChar(sPartName)^, Head.sPartName[0], Min(Length(sPartName), MAX_HEAD_STR));
|
|
// Move(PAnsiChar(sPgName)^, Head.sPgName[0], Min(Length(sPgName), MAX_HEAD_STR));
|
|
|
|
// 헤더 암호화
|
|
ZeroMemory(@ci, SizeOf(ci));
|
|
ZeroMemory(@arrIV, SizeOf(arrIV));
|
|
SetLength(pEncHead, SizeOf(Head) + 16); // 패딩 고려
|
|
|
|
COLibSetCipherInfo(@ci, COLIB_MODE_ARIA, BLOCK_CRYPTO_MODE_CBC, COLIB_PAD_TYPE_PKCS,
|
|
@pDek[0], Length(pDek), @arrIV, SizeOf(arrIV),
|
|
@Head, SizeOf(Head), @pEncHead[0], @nOut);
|
|
|
|
nResult := COLibEncrypt(@ci);
|
|
if nResult = COLIB_SUCCESS then
|
|
begin
|
|
// 암호화된 헤더 길이(Integer, 4byte)를 먼저 쓰고 데이터를 씀 (복호화 시 필요)
|
|
aDstStream.Write(nOut, SizeOf(Integer));
|
|
aDstStream.Write(pEncHead[0], nOut);
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
class function TTgColibDrm.DoEncrypt(sPath: string; pDek: TBytes): Boolean;
|
|
const
|
|
BUF_LEN_64K = 1024 * 64;
|
|
var
|
|
fsSrc, fsDst: TFileStream;
|
|
arrBuf: array [0..BUF_LEN_64K-1] of Byte;
|
|
arrEnc: array [0..BUF_LEN_64K + 31] of Byte; // 패딩 대비 여유 공간
|
|
dwRead: DWORD;
|
|
nOut, nResult: Integer;
|
|
pt: TCOLIB_PAD_TYPE;
|
|
ci: TCIPHER_INFO;
|
|
arrIV: array [0..COLIB_BLOCK_LEN-1] of Byte;
|
|
sSig: AnsiString;
|
|
begin
|
|
sSig := SIG_CO_DRM;
|
|
ZeroMemory(@arrIV, SizeOf(arrIV));
|
|
|
|
Guard(fsSrc, TFileStream.Create(sPath, fmOpenRead));
|
|
Guard(fsDst, TFileStream.Create(sPath + '.enc', fmCreate));
|
|
|
|
// 시그니처 (평문)
|
|
fsDst.Write(sSig[1], Length(sSig));
|
|
|
|
if not SetHead(fsDst, pDek) then Exit;
|
|
|
|
while fsSrc.Position < fsSrc.Size do
|
|
begin
|
|
dwRead := fsSrc.Read(arrBuf, BUF_LEN_64K);
|
|
|
|
// 마지막 블록인 경우 패딩 처리
|
|
if fsSrc.Position >= fsSrc.Size then
|
|
pt := COLIB_PAD_TYPE_PKCS
|
|
else
|
|
pt := COLIB_PAD_TYPE_NO;
|
|
|
|
ZeroMemory(@ci, SizeOf(ci));
|
|
COLibSetCipherInfo(@ci, COLIB_MODE_ARIA, BLOCK_CRYPTO_MODE_CBC, pt,
|
|
@pDek[0], Length(pDek), @arrIV, SizeOf(arrIV),
|
|
@arrBuf, dwRead, @arrEnc, @nOut);
|
|
|
|
nResult := COLibEncrypt(@ci);
|
|
if nResult <> COLIB_SUCCESS then
|
|
begin
|
|
ShowMessage('파일 암호화 실패: ' + IntToStr(nResult));
|
|
Result := False;
|
|
Exit;
|
|
end;
|
|
|
|
fsDst.Write(arrEnc, nOut);
|
|
Result := True;
|
|
|
|
// CBC 모드이므로 다음 블록의 IV는 현재 암호문의 마지막 블록이어야 함 (라이브러리 내부 처리 확인 필요)
|
|
// 만약 라이브러리가 IV를 자동 갱신하지 않는다면 여기서 마지막 암호문 블록을 arrIV에 복사해야 한다.
|
|
end;
|
|
end;
|
|
|
|
class function TTgColibDrm.ExtractHead(aSrcStream: TStream; pDek: TBytes; var aHead: TTgDrmHead): Boolean;
|
|
var
|
|
nEncHeadLen, nOut, nResult: Integer;
|
|
pEncBuf, pDecBuf: TBytes;
|
|
ci: TCIPHER_INFO;
|
|
arrIV: array [0..COLIB_BLOCK_LEN-1] of Byte;
|
|
begin
|
|
Result := False;
|
|
|
|
// 암호화된 헤더 길이 (4바이트)
|
|
if aSrcStream.Read(nEncHeadLen, SizeOf(Integer)) <> SizeOf(Integer) then Exit;
|
|
|
|
// 암호화된 헤더 본문
|
|
SetLength(pEncBuf, nEncHeadLen);
|
|
if aSrcStream.Read(pEncBuf[0], nEncHeadLen) <> nEncHeadLen then Exit;
|
|
|
|
// 헤더 복호화
|
|
SetLength(pDecBuf, nEncHeadLen);
|
|
ZeroMemory(@ci, SizeOf(ci));
|
|
ZeroMemory(@arrIV, SizeOf(arrIV));
|
|
|
|
COLibSetCipherInfo(@ci, COLIB_MODE_ARIA, BLOCK_CRYPTO_MODE_CBC, COLIB_PAD_TYPE_PKCS,
|
|
@pDek[0], Length(pDek), @arrIV, SizeOf(arrIV),
|
|
@pEncBuf[0], nEncHeadLen, @pDecBuf[0], @nOut);
|
|
|
|
nResult := COLibDecrypt(@ci);
|
|
if nResult = COLIB_SUCCESS then
|
|
begin
|
|
Move(pDecBuf[0], aHead, SizeOf(TTgDrmHead));
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
class procedure TTgColibDrm.DoDecrypt(sPath: string; pDek:TBytes);
|
|
const
|
|
BUF_LEN_64K = 1024 * 64;
|
|
var
|
|
fsSrc, fsDst: TFileStream;
|
|
arrBuf: array [0..BUF_LEN_64K - 1] of Byte;
|
|
arrDec: array [0..BUF_LEN_64K + 31] of Byte; // 패딩 대비 여유 공간
|
|
dwRead: DWORD;
|
|
nOut, nResult: Integer;
|
|
pt: TCOLIB_PAD_TYPE;
|
|
ci: TCIPHER_INFO;
|
|
arrIV: array [0..COLIB_BLOCK_LEN - 1] of Byte;
|
|
HeaderInfo: TTgDrmHead; // 복호화된 헤더 정보를 담을 변수
|
|
begin
|
|
Guard(fsSrc, TFileStream.Create(sPath, fmOpenRead or fmShareDenyNone));
|
|
// 기존 파일명에서 .enc가 있다면 제거하고 .dec를 붙임
|
|
Guard(fsDst, TFileStream.Create(ChangeFileExt(sPath, '') + '.dec', fmCreate));
|
|
|
|
try
|
|
// 시그니처 위치(8바이트) 건너뛰기
|
|
fsSrc.Position := Length(SIG_CO_DRM);
|
|
|
|
// 헤더 추출 및 복호화 (데이터 시작 지점으로 스트림 포인터 이동)
|
|
if not ExtractHead(fsSrc, pDek, HeaderInfo) then
|
|
begin
|
|
ShowMessage('헤더 복호화에 실패했습니다. 키 값이 올바르지 않을 수 있습니다.');
|
|
Exit;
|
|
end;
|
|
|
|
// 복호화된 헤더 정보 활용
|
|
// ShowMessage('복호화 대상 사번: ' + string(UTF8String(HeaderInfo.sEmpNo)));
|
|
|
|
ZeroMemory(@arrIV, SizeOf(arrIV));
|
|
|
|
while fsSrc.Position < fsSrc.Size do
|
|
begin
|
|
dwRead := fsSrc.Read(arrBuf, BUF_LEN_64K);
|
|
|
|
// 스트림의 마지막 블록인 경우에만 PKCS 패딩 적용
|
|
if fsSrc.Position >= fsSrc.Size then
|
|
pt := COLIB_PAD_TYPE_PKCS
|
|
else
|
|
pt := COLIB_PAD_TYPE_NO;
|
|
|
|
ZeroMemory(@ci, SizeOf(ci));
|
|
COLibSetCipherInfo(@ci, COLIB_MODE_ARIA, BLOCK_CRYPTO_MODE_CBC, pt,
|
|
@pDek[0], Length(pDek), @arrIV, SizeOf(arrIV),
|
|
@arrBuf, dwRead, @arrDec, @nOut);
|
|
|
|
nResult := COLibDecrypt(@ci);
|
|
if nResult <> COLIB_SUCCESS then
|
|
begin
|
|
ShowMessage('데이터 복호화 중 오류 발생: ' + IntToStr(nResult));
|
|
Exit;
|
|
end;
|
|
|
|
fsDst.Write(arrDec, nOut);
|
|
end;
|
|
|
|
//ShowMessage('복호화가 완료되었습니다.');
|
|
|
|
except
|
|
on E: Exception do
|
|
ShowMessage('복호화 중 예외 발생: ' + E.Message);
|
|
end;
|
|
end;
|
|
|
|
end.
|