昨天有人给了我一段据说是根据网上的 java 程序改编的计算 md5 值的 vfp 代码,他说与他使用 md5 计算工具得到的 md5 值不同,想让我看看是哪里改编时出了问题。粗略看了一下,其实这段代码十分眼熟,在 csdn 的 vfp 讨论区中就曾出现过,只是我以前没有需要,所以都是一眼而过,但对那段长长的 hash 变换表却印象深刻。由于我也不知道具体的 md5 值算法,所以也只能先看那段 java 代码,并未发现转换中有何不妥之处。转念一想,Windows Crypt API 中不是也有计算 hash 值的函数吗?只是以前没用过,不熟悉,打开 msdn 找到 Crypt API 这一篇仔细阅读,再参考网上的一些资料,于是有了下面这段代码。
FUNCTION GetMD5( tcData )
#DEFINE PROV_RSA_FULL 1
#DEFINE CRYPT_VERIFYCONTEXT 0xf0000000
#DEFINE CALG_MD5 0x00008003
#DEFINE HP_HASHVAL 0x0002
DECLARE long GetLastError IN WIN32API
DECLARE long CryptAcquireContext IN WIN32API ;
long @ lhProvHandle, string cContainer, ;
string cProvider, long nProvType, long nFlags
DECLARE long CryptCreateHash IN WIN32API ;
long hProviderHandle, long nALG_ID, long hKeyhandle, ;
long nFlags, long @ hCryptHashHandle
DECLARE long CryptHashData IN WIN32API ;
long hHashHandle, string @ cData, long nDataLen, long nFlags
DECLARE long CryptGetHashParam IN WIN32API ;
long hHashHandle, long nParam, ;
string @ cHashValue, long @ nHashSize, long nFlags
DECLARE long CryptDestroyHash IN WIN32API ;
long hKeyHandle
DECLARE long CryptReleaseContext IN WIN32API ;
long hProvHandle, long nReserved
LOCAL lnStatus, loError, lhProvider, lhHashObject, lnDataSize, ;
lcHashValue, lnHashSize
lhProvider = 0
lhHashObject = 0
lnDataSize = LEN(tcData)
lcHashValue = REPLICATE(CHR(0), 16)
lnHashSize = LEN(lcHashValue)
TRY
* 取加密驱动提供程序上下文
lnStatus = CryptAcquireContext(@ lhProvider, 0, 0, ;
PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)
IF lnStatus = 0
ERROR GetLastError()
ENDIF
* 创建一个使用 md5 算法的哈希对象
lnStatus = CryptCreateHash(lhProvider, CALG_MD5, 0, 0, @ lhHashObject)
IF lnStatus = 0
ERROR GetLastError()
ENDIF
* 将要计算 md5 值的数据提供给这个哈希对象中
lnStatus = CryptHashData(lhHashObject, tcData, lnDataSize, 0)
IF lnStatus = 0
ERROR GetLastError()
ENDIF
* 获取哈希值. 如果调用者没有提供足够的缓冲区(计算 md5 只需要 16 个字节),
* 函数调用失败并设置错误码 ERROR_MORE_DATA, 参数 lnHashSize 中包含需要
* 的缓冲区大小
lnStatus = CryptGetHashParam( ;
lhHashObject, HP_HASHVAL, @ lcHashValue, @ lnHashSize, 0)
IF lnStatus = 0
ERROR GetLastError()
ENDIF
CATCH TO loError
MESSAGEBOX(‘HashMD5 执行失败,错误码: ‘ + TRANSFORM(loError.ErrorNo, ‘@0’), ;
‘错误’, 16)
FINALLY
* 释放哈希对象
IF 0 != lhHashObject
CryptDestroyHash(lhHashObject)
ENDIF
* 释放加密驱动提供程序上下文
IF 0 != lhProvider
CryptReleaseContext(lhProvider, 0)
ENDIF
ENDTRY
RETURN STRCONV(lcHashValue, 15)
ENDFUNC
兼回答 tszsc 朋友的问题:
另一种常见的文件/字符串校验方法是SHA1(也称SHA160),它产生的是160/8=20个字节的验证码,比MD5的16个字节稍长一点,只需将上面的代码稍作改造即可实现:
1. 加一个常量定义:#define CALG_SHA1 0x00008004
2. 将 lcHashValue = REPL… 这一句中的 16 改为 20
3. 将调用 CryptCreateHash 函数的第二个参数改成 CALG_SHA1
函数名改为 GetSHA1,如果你有空的话,将错误提示和注释中的 MD5 字样也改过来就更好了。
回答 cslx0810 朋友
这个问题提得有道理,我原来没想将这个函数用来计算超大文件,只想偶尔用来校验一些小数据量的值
下面是可以计算超大文件 md5 值的示例代码,是在上面代码的基础上做了少量修改
我的电脑比较差,用这段代码来计算一个700M的光盘ISO映像文件,用时一般在30秒左右
不过这可能除了跟 CPU/RAM 有关以外,跟文件是否有碎片以及硬盘速度也有很大关系
另外,代码中 BYTES_PER_READ 常量的取值也会对整个计算速度产生较大的影响
我试验的结果是一般要控制在256kb以内,再大就不仅不会快,反而更慢,大概就是俗话“贪多嚼不烂”的道理吧
Function MyProgress
Lparameters tnNewVal
* 简单的用 wait 窗口显示当前计算进度,你可以用一个进度条来改善用户界面
Wait Window Transform( Int( 100 * m.tnNewVal )) + ‘%’ Nowait Noclear
Endfunc
Function GetFileMD5( tcFile, tcBackcallFunc )
#Define PROV_RSA_FULL 1
#Define CRYPT_VERIFYCONTEXT 0xf0000000
#Define CALG_MD5 0x00008003
#Define HP_HASHVAL 0x0002
#Define GENERIC_READ 0x80000000
#Define FILE_SHARE_READ 0x00000001
#Define OPEN_EXISTING 3
#Define FILE_FLAG_SEQUENTIAL_SCAN 0x08000000
* 计算超大文件 md5 值时, 每次最多要读取的字节数, 缺省读 64KB 的内容
#Define BYTES_PER_READ 1024 * 64
Declare Long GetLastError In WIN32API
Declare Long CryptAcquireContext In WIN32API ;
Long @ lhProvHandle, String cContainer, ;
String cProvider, Long nProvType, Long nFlags
Declare Long CryptCreateHash In WIN32API ;
Long hProviderHandle, Long nALG_ID, Long hKeyhandle, ;
Long nFlags, Long @ hCryptHashHandle
Declare Long CryptHashData In WIN32API ;
Long hHashHandle, String @ cData, Long nDataLen, Long nFlags
Declare Long CryptGetHashParam In WIN32API ;
Long hHashHandle, Long nParam, ;
String @ cHashValue, Long @ nHashSize, Long nFlags
Declare Long CryptDestroyHash In WIN32API ;
Long hKeyHandle
Declare Long CryptReleaseContext In WIN32API ;
Long hProvHandle, Long nReserved
Declare Long CreateFile In WIN32API ;
String lpFileName, Long dwDesiredAccess, Long dwShareMode, ;
String lpSecurityAttributes, Long dwCreationDisposition, ;
Long dwFlagsAndAttributes, Long hTemplateFile
Declare Long ReadFile In WIN32API ;
Long hFile, String @ lpBuffer, Long nNumberOfBytesToRead, ;
Long @ lpNumberOfBytesRead, Long lpOverlapped
Declare Long GetFileSizeEx In WIN32API ;
Long hFile, String @ lpFileSize
Declare Long CloseHandle In WIN32API Long hObject
Local lnStatus, loError, lhProvider, lhHashObj, lcHashValue, lnHashSize
Local lhFile, lnFileSize, lnTotalBytesRead, lnBytesRead, lcBuffer
m.lhFile = -1
m.lnFileSize = 0
m.lhProvider = 0
m.lhHashObj = 0
m.lnHashSize = 16
m.lcHashValue = Replicate( Chr(0), m.lnHashSize )
Try
* 打开要计算 md5 值的文件
* 这里没有用 vfp 的 fopen/fread 来操作文件, 因为它不能处理超过 2G 的文件
* 且一次最多只能读取 64K 的数据
m.lhFile = CreateFile( m.tcFile, GENERIC_READ, FILE_SHARE_READ, ;
NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0 )
If ( -1 == m.lhFile )
Error GetLastError()
Endif
* 由于可能要计算超大文件( > 2G ), 所以要用 GetFileSizeEx 来获取文件大小
m.lcFileSize = Replicate( Chr(0), 8 )
GetFileSizeEx( m.lhFile, @ m.lcFileSize )
m.lnFileSize = ;
ASC( Substr( m.lcFileSize, 1, 1 )) ;
+ Asc( Substr( m.lcFileSize, 2, 1 )) * 0x100 ;
+ Asc( Substr( m.lcFileSize, 3, 1 )) * 0x10000 ;
+ Asc( Substr( m.lcFileSize, 4, 1 )) * 0x1000000 ;
+ Asc( Substr( m.lcFileSize, 5, 1 )) * 0x100000000 ;
+ Asc( Substr( m.lcFileSize, 6, 1 )) * 0x10000000000
* 取加密驱动提供程序上下文
m.lnStatus = CryptAcquireContext( @ m.lhProvider, 0, 0, ;
PROV_RSA_FULL, CRYPT_VERIFYCONTEXT )
If ( 0 == m.lnStatus )
Error GetLastError()
Endif
* 创建一个使用 md5 算法的哈希对象
m.lnStatus = CryptCreateHash( m.lhProvider, CALG_MD5, 0, 0, @ m.lhHashObj )
If ( 0 == m.lnStatus )
Error GetLastError()
Endif
* 循环读取文件的各分段并计算分段的 md5 值
m.lnTotalBytesRead = 0
m.lnBytesRead = 0
m.lcBuffer = Replicate( Chr(0), BYTES_PER_READ )
Do While ( 0 != ReadFile( ;
m.lhFile, @ m.lcBuffer, BYTES_PER_READ, @ m.lnBytesRead, 0 ))
If ( 0 == m.lnBytesRead )
Exit
Endif
* 将要计算 md5 值的数据提供给这个哈希对象中
m.lnStatus = CryptHashData( m.lhHashObj, m.lcBuffer, m.lnBytesRead, 0 )
If ( 0 == m.lnStatus )
Error GetLastError()
Endif
m.lnTotalBytesRead = m.lnTotalBytesRead + m.lnBytesRead
* 调用回调函数显示当前进度
Evaluate( m.tcBackcallFunc ;
+ ‘( ‘ + Transform( m.lnTotalBytesRead / m.lnFileSize ) + ‘)’ )
Enddo
* 获取哈希值
m.lnStatus = CryptGetHashParam( ;
m.lhHashObj, HP_HASHVAL, @ m.lcHashValue, @ m.lnHashSize, 0 )
If ( 0 == m.lnStatus )
Error GetLastError()
Endif
Catch To m.loError
Messagebox(‘HashMD5 执行失败,错误码: ‘ + Transform( m.loError.ErrorNo, ‘@0’ ))
m.lcHashValue = ”
Finally
* 释放哈希对象
If ( 0 != m.lhHashObj )
CryptDestroyHash( m.lhHashObj )
Endif
* 释放加密驱动提供程序上下文
If ( 0 != m.lhProvider )
CryptReleaseContext( m.lhProvider, 0 )
Endif
If ( -1 != m.lhFile )
CloseHandle( m.lhFile )
Endif
Endtry
Return Strconv( m.lcHashValue, 15 )
Endfunc
今天的文章利用 Windows Crypt API 获取 MD5/SHA1 值分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/31643.html