使用CRAM-MD5的SMTP AUTH

时间:2012-05-09 05:06:40

标签: python smtp

按照SMTP with CRAM-MD5 in Java中给出的指导原则,我在Python中编写了一个小程序来计算响应时将nonce作为输入:

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服务器拒绝此程序生成的响应。有人可以指出我做错了吗?

3 个答案:

答案 0 :(得分:2)

使用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()

答案 1 :(得分:1)

您可以使用hmac module来计算,或至少仔细检查输出。

您使用的是Python2.x还是3.x?您也可能遇到一些字节/字符串问题。

具体来说,在字节变换后,pwd.decode()可能会给你带来垃圾,因为它试图理解那些不再是字符数据的东西。

您似乎也缺少将密钥块扩展到散列函数的输入块大小的倍数的步骤。

HMAC的wikipedia文章在Python中包含了一个可能有用的小例子。

答案 2 :(得分:0)

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

  1. 您不仅需要xor密码,还需要所有64字节
  2. 由于md5.update()使用二进制数据
  3. ,因此不应解码密钥
  4. 出于同样的原因,您需要调用m1.digest()而不是m1.hexdigest()
  5. 您的代码包含我的修复程序和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)