UWSCでWin32 Crypto APIを使う

Crypto APIをラップするモジュールを書いてみた。
Crypto APIVISTA以降Crypto Next Generationに更新されていますが、とりあえず旧版で。



スクリプト

Crypto.uws

OPTION EXPLICIT

IFB GET_UWSC_NAME = "Crypto.uws" THEN
	DIM pass = "password", text = "original text 日本語", i, res
	DIM iv[] = $EB,$F5,$F2,$08,$35,$9C,$F1,$D9,$1B,$9D,$62,$86,$CF,$C6,$34,$61
	DIM encdata = "oWsLLxr8ZxdFVZqWzwts5YrZGujDxPkE04q4n3MPqMM="

	Crypto.GenKey(pass)
	DIM blockSize = Crypto.GetBlockSize()
	//DIM iv = Crypto.GenRandom(blockSize / 8)
	//PRINT Crypto.BinToString(iv, -1, 1)
	Crypto.SetKeyParam(1, iv)	// KP_IV

	IFB MSGBOX("Test Encrypt?", BTN_YES OR BTN_NO) = BTN_YES THEN
		res = TRIM(Crypto.EncryptStrToString(text))
		i = (res = encdata)
	ELSE
		res = TRIM(Crypto.DecryptStrToString(encdata))
		i = (res = text)
	ENDIF
	IF i THEN
		PRINT "OK: " + res
	ELSE
		PRINT "NG: " + res
	ENDIF

	Crypto.DestroyKey()
	Crypto.ReleaseProvider()	// 最後に引数なしのReleaseProviderを呼ぶこと
ENDIF


MODULE Crypto
	DEF_DLL GetLastError(): DWORD: kernel32
	DEF_DLL CryptGenRandom(DWORD,DWORD,BYTE[]): bool: advapi32
	DEF_DLL CryptBinaryToStringA(BYTE[],DWORD,DWORD,var string,var DWORD): bool: crypt32
	DEF_DLL CryptStringToBinaryA(string,DWORD,DWORD,var BYTE[],var DWORD,DWORD,var DWORD): bool: crypt32
	DEF_DLL CryptEncrypt(DWORD,DWORD,bool,DWORD,var BYTE[],var DWORD,DWORD): bool: advapi32
	DEF_DLL CryptDecrypt(DWORD,DWORD,bool,DWORD,var BYTE[],var DWORD): bool: advapi32

	DIM _hProv = 0, _hKey = 0

	PROCEDURE Crypto
		_hProv = GetProvider()
	FEND

	FUNCTION GetProvider(provTypeGet=24)
		// プロバイダーの取得(初期値は RSA Full and AES)
		RESULT = 0
		DEF_DLL CryptEnumProvidersW(DWORD,DWORD,DWORD,var DWORD,var wstring,var DWORD): bool: advapi32
		DEF_DLL CryptAcquireContextW(var DWORD,DWORD,wstring,DWORD,DWORD): bool: advapi32
		DIM i = 0, provType, provName, provNameLen, loop = TRUE
		WHILE loop
			loop = CryptEnumProvidersW(i, 0, 0, provType, NULL, provNameLen)
			IFB loop THEN
				provName = FORMAT(CHR(0), provNameLen)
				loop = CryptEnumProvidersW(i, 0, 0, provType, provName, provNameLen)
				IFB loop AND provType = provTypeGet THEN
					loop = !CryptAcquireContextW(RESULT, 0, provName, provType, $F0000000)
					IFB loop THEN
						RESULT = 0
						Log("CryptAcquireContext Error " + GetLastError())
					ENDIF
				ENDIF
			ENDIF
			i = i + 1
		WEND
	FEND

	FUNCTION GenRandom(len, hProv=0)
		RESULT = SPLIT(EMPTY)
		IF hProv = 0 THEN hProv = _hProv
		IFB len > 0 THEN
			DIM buf[len-1]
			IFB CryptGenRandom(hProv, len, buf) THEN
				RESULT = SetResult(buf)
			ELSE
				RESULT = GetLastError()
				Log("CryptGenRandom Error " + RESULT)
				IF RESULT = 0 THEN RESULT = -1
			ENDIF
		ENDIF
	FEND

	FUNCTION BinToString(data[], len=-1, flags=11)	// CRYPT_STRING_HEXASCIIADDR
		IF len < 0 THEN len = LENGTH(data)
		DIM l = 0
		IFB CryptBinaryToStringA(data, len, flags, NULL, l) THEN
			RESULT = FORMAT(CHR(0), l)
			IFB !CryptBinaryToStringA(data, len, flags, RESULT, l) THEN
				RESULT = GetLastError()
				Log("CryptBinaryToString Error " + RESULT)
				IF RESULT = 0 THEN RESULT = -1
			ENDIF
		ELSE
			RESULT = GetLastError()
			Log("CryptBinaryToString calc Error " + RESULT)
			IF RESULT = 0 THEN RESULT = -1
		ENDIF
	FEND
	FUNCTION StrToBinary(data, flags=8)
		DIM size = 0
		IFB CryptStringToBinaryA(data, 0, flags, NULL, size, 0, flags) AND size > 0 THEN
			DIM res[size-1]
			IFB CryptStringToBinaryA(data, 0, flags, res, size, 0, flags) THEN
				RESULT = SetResult(res)
			ELSE
				RESULT = GetLastError()
				Log("CryptStringToBinary Error " + RESULT)
				IF RESULT = 0 THEN RESULT = -1
			ENDIF
		ELSE
			RESULT = GetLastError()
			Log("CryptStringToBinary calc Error " + RESULT)
			IF RESULT = 0 THEN RESULT = -1
		ENDIF
	FEND

	FUNCTION GenKey(pass, hashAlg=$8004, keyAlg=$6610, keyLen=256, hProv=0)
		// 鍵の取得(初期値は、CALG_SHA,CALG_AES_256)
		RESULT = 0
		IF hProv = 0 THEN hProv = _hProv

		// ハッシュの取得
		DIM hHash = 0
		DEF_DLL CryptCreateHash(DWORD,DWORD,DWORD,DWORD,var DWORD): bool: advapi32
		IFB !CryptCreateHash(hProv, hashAlg, 0, 0, hHash) THEN
			Log("CryptCreateHash Error " + GetLastError())
		ENDIF

		// ハッシュの計算
		DEF_DLL CryptHashData(DWORD,string,DWORD,DWORD): bool: advapi32
		IFB !CryptHashData(hHash, pass, LENGTHB(pass), 0) THEN
			Log("CryptHashData Error " + GetLastError())
		ENDIF

		// 鍵の生成
		DEF_DLL CryptDeriveKey(DWORD,DWORD,DWORD,DWORD,var DWORD): bool: advapi32
		IFB !CryptDeriveKey(hProv, keyAlg, hHash, keyLen*$10000, RESULT) THEN
			Log("CryptDeriveKey Error " + GetLastError())
		ENDIF

		// ハッシュの破棄
		DEF_DLL CryptDestroyHash(DWORD): bool: advapi32
		IFB hHash <> 0 AND !CryptDestroyHash(hHash) THEN
			Log("CryptDestroyHash Error " + GetLastError())
		ENDIF

		// パラメーターの設定
		SetKeyParamDword(3, 1, RESULT)	// KP_PADDING,PKCS5_PADDING
		SetKeyParamDword(4, 4, RESULT)	// KP_MODE,CRYPT_MODE_CFB

		IFB RESULT THEN
			IF _hKey THEN DestroyKey(_hKey)
			_hKey = RESULT
		ENDIF
	FEND

	FUNCTION SetKeyParam(param,data[],hKey=0)
		RESULT = 0
		IF hKey = 0 THEN hKey = _hKey
		DEF_DLL CryptSetKeyParam(DWORD,DWORD,BYTE[],DWORD): bool: advapi32
		IFB !CryptSetKeyParam(hKey, param, data, 0) THEN
			RESULT = GetLastError()
			Log("CryptSetKeyParam Error " + RESULT)
			IF RESULT = 0 THEN RESULT = -1
		ENDIF
	FEND
	FUNCTION SetKeyParamDword(param,data,hKey=0)
		RESULT = 0
		IF hKey = 0 THEN hKey = _hKey
		DEF_DLL CryptSetKeyParam(DWORD,DWORD,var DWORD,DWORD): bool: advapi32
		IFB !CryptSetKeyParam(hKey, param, data, 0) THEN
			RESULT = GetLastError()
			Log("CryptSetKeyParam DWORD Error " + RESULT)
			IF RESULT = 0 THEN RESULT = -1
		ENDIF
	FEND

	FUNCTION GetBlockSize(hKey=0)
		// ブロックサイズ取得
		RESULT = 0
		IF hKey = 0 THEN hKey = _hKey
		DIM i = 4
		DEF_DLL CryptGetKeyParam(DWORD,DWORD,var DWORD,var DWORD,DWORD): bool: advapi32
		IFB !CryptGetKeyParam(hKey, 8, RESULT, i, 0) THEN	// KP_BLOCKLEN
			Log("CryptGetKeyParam Error " + GetLastError())
		ENDIF
	FEND

	FUNCTION Encrypt(var data[], var len, final=TRUE, hKey=0)
		RESULT = 0
		IF hKey = 0 THEN hKey = _hKey
		DIM bufLen = LENGTH(data)
		IFB !CryptEncrypt(hKey, 0, final, 0, data, len, bufLen) THEN
			RESULT = GetLastError()
			Log("CryptEncrypt Error " + RESULT)
			IF RESULT = 0 THEN RESULT = -1
		ENDIF
	FEND
	FUNCTION EncryptToString(data[], len=-1, final=TRUE, flags=1, hKey=0)
		RESULT = EMPTY
		IF hKey = 0 THEN hKey = _hKey
		IF len < 0 THEN len = LENGTH(data)
		// 必要なサイズを用意する。共通鍵暗号化はブロックサイズに切り上げで良いはず
		DIM blockSize = GetBlockSize(hKey) / 8, m = len MOD blockSize
		blockSize = len
		IF m > 0 THEN blockSize = len + (blockSize - m)
		DIM res[blockSize]
		DEF_DLL RtlMoveMemory(var BYTE[], BYTE[], DWORD): kernel32
		RtlMoveMemory(res, data, len)
		IFB Encrypt(res, len, final, hKey) = 0 THEN
			RESULT = BinToString(res, len, flags)
		ENDIF
	FEND
	FUNCTION EncryptStrToString(text, final=TRUE, flags=1, hKey=0)
		IF hKey = 0 THEN hKey = _hKey
		DIM m = LENGTHB(text), i, data[m]
		DEF_DLL RtlMoveMemory(var BYTE[], string, DWORD): kernel32
		RtlMoveMemory(data, text, m)
		RESULT = EncryptToString(data, m, final, flags, hKey)
	FEND

	FUNCTION Decrypt(var data[], var len, final=TRUE, hKey=0)
		RESULT = 0
		IF hKey = 0 THEN hKey = _hKey
		IFB !CryptDecrypt(hKey, 0, final, 0, data, len) THEN
			RESULT = GetLastError()
			Log("CryptDecrypt Error " + RESULT)
			IF RESULT = 0 THEN RESULT = -1
		ENDIF
	FEND
	FUNCTION DecryptToString(data[], len=-1, final=TRUE, hKey=0)
		RESULT = EMPTY
		IF hKey = 0 THEN hKey = _hKey
		IF len < 0 THEN len = LENGTH(data)
		IFB Decrypt(data, len, final, hKey) = 0 THEN
			RESULT = FORMAT(CHR(0), len)
			DEF_DLL RtlMoveMemory(var string, BYTE[], DWORD): kernel32
			RtlMoveMemory(RESULT, data, len)
		ENDIF
	FEND
	FUNCTION DecryptStrToString(encText, final=TRUE, flags=1, hKey=0)
		RESULT = EMPTY
		IF hKey = 0 THEN hKey = _hKey
		DIM data = StrToBinary(encText, flags)
		IFB VARTYPE(data) >= VAR_ARRAY THEN
			RESULT = DecryptToString(data, LENGTH(data), final, hKey)
		ENDIF
	FEND

	FUNCTION DestroyKey(hKey=0)
		// 鍵の破棄 もしかして不要?
		RESULT = 0
		IF hKey = 0 THEN hKey = _hKey
		DEF_DLL CryptDestroyKey(DWORD): bool: advapi32
		IFB hKey <> 0 AND !CryptDestroyKey(hKey) THEN
			RESULT = GetLastError()
			Log("CryptDestroyKey Error " + RESULT)
			IF RESULT = 0 THEN RESULT = -1
		ENDIF
		IF hKey = _hKey THEN _hKey = 0
	FEND

	FUNCTION ReleaseProvider(hProv=0)
		// プロバイダーの解放
		RESULT = 0
		IF hProv = 0 THEN hProv = _hProv
		DEF_DLL CryptReleaseContext(DWORD,DWORD): bool: advapi32
		IFB hProv <> 0 AND !CryptReleaseContext(hProv, 0) THEN
			RESULT = GetLastError()
			Log("CryptReleaseContext Error " + RESULT)
			IF RESULT = 0 THEN RESULT = -1
		ENDIF
		IF hProv = _hProv THEN _hProv = 0
	FEND


	PROCEDURE Log(msg)
		PRINT msg
	FEND

	FUNCTION SetResult(data[], len=-1, offset=0, i=0)
		IF len < 0 THEN len = LENGTH(data)
		len = len - 1
		RESULT = SPLIT(EMPTY)
		IFB len >= 0 THEN
			RESULT = SPLIT(" ")
			RESIZE(RESULT, len)
			FOR i = 0 TO len
				RESULT[i] = data[i + offset]
			NEXT
		ENDIF
	FEND

ENDMODULE


GenRandom関数は、数学的により安全な乱数が生成できます。
基本的には、例にあるように、
パスワードでGenKeyして、SetKeyParamで適当に初期化ベクター(IV)を設定して、暗号化(Encrypt)か複合化(Decrypt)して、DestroyKey、ReleaseProviderする。


初期状態でのGenKeyは、AES暗号化なので、充分な強度があります。
AES暗号化では、暗号キー(パスワード)と初期化ベクターが重要なので、その二つがキーと思うと良いでしょう。
サンプルには初期化ベクターをRandomで生成している例がコメントとして書かれていますが、それを使うと暗号化時と複合化時で初期化ベクターが異なるため、複合化できなくなります。

雑感

つい最近、SafeArray関数に気づきました。
SPLIT(" ")のトリックはいらないですね、、、。