使用 CRAM-MD5 的 SMTP 身份验证



按照 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小例子。

我分析了你的代码并发现了错误:

  1. 您不仅需要xor密码,还需要所有64字节
  2. 密钥不应该被解码,因为 md5.update() 处理二进制数据
  3. 出于同样的原因,您需要调用 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)

相关内容

最新更新