按照 Java 中 CRAM-MD5 在 SMTP 中给出的准则,我用 Python 编写了一个小程序来计算给定随机数作为输入时的响应:
import hashlib
from base64 import b64encode, b64decode
import sys
from decimal import *
#MD5(('secret' XOR opad), MD5(('secret' XOR ipad), challenge))
#opad - 0x5C, ipad - 0x36.
def main(nonce):
pwd = bytearray("password")
for i in range(len(pwd)):
pwd[i] = pwd[i] ^ 0x36
m1 = hashlib.md5()
m1.update(pwd.decode())
m1.update(b64decode(nonce))
m2 = hashlib.md5()
pwd = bytearray("password")
for i in range(len(pwd)):
pwd[i] = pwd[i] ^ 0x5C
m2.update(pwd.decode())
m2.update(m1.hexdigest())
print b64encode("username " + m2.hexdigest())
if __name__ == "__main__":
if (len(sys.argv) != 2):
print("ERROR usage: smtp-cram-md5 <nonce>")
else:
main(sys.argv[1])
但是,SMTP 服务器拒绝我给出的由该程序生成的响应。有人可以指出我做错了什么吗?
使用 HMAC 实现 CRAM-MD5 的示例。使用 python2.7 和 python3.4 进行测试。在Python 3上,可以通过将hashlib.md5替换为"md5"来避免hashlib导入。
"""
doc-testing with example values from RFC 2195
>>> challenge = 'PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+'
>>> user = 'tim'
>>> password = 'tanstaaftanstaaf'
>>> target_response = 'dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw'
>>> actual_response = cram_md5(user, password, challenge)
>>> target_response == actual_response
True
"""
import base64
import hashlib
import hmac
def cram_md5(user, password, challenge):
password = password.encode('utf-8')
challenge = base64.b64decode(challenge)
digest = hmac.HMAC(password, challenge, hashlib.md5).hexdigest()
response = '{} {}'.format(user, digest).encode()
return base64.b64encode(response).decode()
if __name__ == "__main__":
import doctest
doctest.testmod()
您可以使用 hmac 模块来计算此值,或者至少仔细检查输出。
您使用的是 Python2.x 还是 3.x?您可能也有一些字节/字符串问题。
具体来说,在字节咀嚼之后,pwd.decode()可能会给你垃圾,因为它试图理解不再是字符数据的东西。
您似乎还缺少将密钥块扩展到哈希函数输入块大小的倍数的步骤。
HMAC的维基百科文章包括一个可能有用的Python小例子。
我分析了你的代码并发现了错误:
- 您不仅需要xor密码,还需要所有64字节
- 密钥不应该被解码,因为 md5.update() 处理二进制数据
- 出于同样的原因,您需要调用 m1.digest() 而不是 m1.hexdigest()
您的代码与我的修复和py3k兼容性:
import hashlib
from base64 import b64encode, b64decode
import sys
def main(nonce):
pwd = bytearray('password'.encode('utf-8'))
key = bytearray(64*b'x36')
for i in range(len(pwd)):
key[i] ^= pwd[i]
m1 = hashlib.md5()
m1.update(key)
m1.update(b64decode(nonce))
m2 = hashlib.md5()
key = bytearray(64*b'x5c')
for i in range(len(pwd)):
key[i] ^= pwd[i]
m2.update(key)
m2.update(m1.digest())
response = "username " + m2.hexdigest()
print(b64encode(response.encode('utf-8')).decode('ascii'))
if __name__ == "__main__":
if (len(sys.argv) != 2):
print("ERROR usage: smtp-cram-md5 <nonce>")
else:
main(sys.argv[1])
免責聲明:此代码仅对密码长度不超过 64 字节有效!(请参阅 RFC 2195)