zoukankan      html  css  js  c++  java
  • Python3标准库:hmac密码信息签名与验证

    1. hmac密码信息签名与验证

    HMAC算法可以用于验证信息的完整性,这些信息可能在应用之间传递,或者存储在一个可能有安全威胁的地方。基本思想是生成实际数据的一个密码散列,并提供一个共享的秘密密钥。然后使用得到的散列检查所传输或存储的信息,以确定一个信任级别,而不是传输秘密密钥。

    1.1 消息签名

    new()函数会创建一个新对象来计算消息签名。下面这个例子使用了默认的MD5散列算法。

    import hmac
    
    digest_maker = hmac.new(b'secret-shared-key-goes-here')
    
    with open('lorem.txt', 'rb') as f:
        while True:
            block = f.read(1024)
            if not block:
                break
            digest_maker.update(block)
    
    digest = digest_maker.hexdigest()
    print(digest)

    运行这段代码时,会读取一个数据文件,并为它计算一个HMAC签名。

    1.2 候选摘要类型

    尽管hmac的默认密码算法是MD5,但这并不是最安全的方法。MD5散列有一些缺点,如冲突(两个不同的消息生成相同的散列)。一般认为SHA1算法更健壮,更建议使用。 

    import hmac
    import hashlib
    
    digest_maker = hmac.new(
        b'secret-shared-key-goes-here',
        b'',
        hashlib.sha1,
    )
    
    with open('demo.py', 'rb') as f:
        while True:
            block = f.read(1024)
            if not block:
                break
            digest_maker.update(block)
    
    digest = digest_maker.hexdigest()
    print(digest)

    new()函数有3个参数。第1个参数是秘密密钥,这个密钥会在通信双方之间共享,使两端都可以使用相同的值。第2个值是一个初始消息。如果需要认证的消息内容很小,如一个时间戳或一个HTTP POST,则把整个消息体都传递到new()而不是使用update()方法。最后一个参数是要使用的摘要模块。默认为hashlib.md5,不过这个例子传入了'sha1',其会让hmac使用hashlib.sha1。

    1.3 二进制摘要

    前面的例子使用hexdigest()方法来生成可打印的摘要。hexdigest是digest()方法计算的值的一个不同表示,这是一个二进制值,可以包括不可打印的字符(包括NUL)。有些Web服务(Google checkout、Amazon S3)会使用base64编码版本的二进制摘要而不是hexdigest。

    import base64
    import hmac
    import hashlib
    
    with open('lorem.txt', 'rb') as f:
        body = f.read()
    
    hash = hmac.new(
        b'secret-shared-key-goes-here',
        body,
        hashlib.sha1,
    )
    
    digest = hash.digest()
    print(base64.encodebytes(digest))

    base64编码串以一个换行符结束,在HTTP首部或其他格式敏感的上下文中嵌入这个串时,通常需要去除这个换行符。

     

    1.4 消息签名的应用

    对于所有公共网络服务,在安全性要求很高的地方存储数据,就应当使用HMAC认证。例如,通过一个管道或套接字发送数据时,应当对数据进行签名,然后在使用这个数据之前要检查这个签名。文件hmac_pickle.py中给出了一个扩展例子。

    第一步是建立一个函数,计算一个串的摘要,另外实例化一个简单的类,并通过一个通信通道传递。 

    import hashlib
    import hmac
    import io
    import pickle
    import pprint
    
    def make_digest(message):
        "Return a digest for the message."
        hash = hmac.new(
            b'secret-shared-key-goes-here',
            message,
            hashlib.sha1,
        )
        return hash.hexdigest().encode('utf-8')
    
    class SimpleObject:
        """Demonstrate checking digests before unpickling.
        """
    
        def __init__(self, name):
            self.name = name
    
        def __str__(self):
            return self.name

    接下来,创建一个ByteIO缓冲区表示这个套接字或管道。这个例子对数据流使用了一种易于解析的原生格式。首先写出摘要以及数据长度,后面是一个换行符。接下来是对象的串行化表示(由pickle生成)。实际的系统可能不希望依赖于一个长度值,毕竟如果摘要不正确,这个长度可能也是错误的。更适合的做法是使用真实数据中不太可能出现的某个终止符序列。

    然后示例程序向流写两个对象。写第一个对象时使用了正确的摘要值。

    # Simulate a writable socket or pipe with a buffer
    out_s = io.BytesIO()
    
    # Write a valid object to the stream:
    #  digest
    length
    pickle
    o = SimpleObject('digest matches')
    pickled_data = pickle.dumps(o)
    digest = make_digest(pickled_data)
    header = b'%s %d
    ' % (digest, len(pickled_data))
    print('WRITING: {}'.format(header))
    out_s.write(header)
    out_s.write(pickled_data)

    再用一个不正确的摘要将第二个对象写入流,这个摘要是为其他数据计算的,而并非由pickle生成。

    # Write an invalid object to the stream
    o = SimpleObject('digest does not match')
    pickled_data = pickle.dumps(o)
    digest = make_digest(b'not the pickled data at all')
    header = b'%s %d
    ' % (digest, len(pickled_data))
    print('
    WRITING: {}'.format(header))
    out_s.write(header)
    out_s.write(pickled_data)
    
    out_s.flush()

    既然数据在BytesIO缓冲区中,那么可以将它再次读出。首先读取包含摘要和数据长度的数据行,然后使用得到的长度值读取其余的数据。pickle.load()可以直接从流读数据,不过这种策略有一个假设,认为它是一个可信的数据流,但这个数据还不能保证足够可信到可以解除pickled。可以将pickle作为一个串从流读取,而不是真正将对象解除pickled,这样会更为安全。

    # Simulate a readable socket or pipe with a buffer
    in_s = io.BytesIO(out_s.getvalue())
    
    # Read the data
    while True:
        first_line = in_s.readline()
        if not first_line:
            break
        incoming_digest, incoming_length = first_line.split(b' ')
        incoming_length = int(incoming_length.decode('utf-8'))
        print('
    READ:', incoming_digest, incoming_length)

    一旦pickled数据在内存中,那么可以重新计算摘要值,并使用compare_digest()与所读取的数据进行比较。如果摘要匹配,就可以信任这个数据,并对其解除pickled。

        incoming_pickled_data = in_s.read(incoming_length)
    
        actual_digest = make_digest(incoming_pickled_data)
        print('ACTUAL:', actual_digest)
    
        if hmac.compare_digest(actual_digest, incoming_digest):
            obj = pickle.loads(incoming_pickled_data)
            print('OK:', obj)
        else:
            print('WARNING: Data corruption')

    输出显示第一个对象通过验证,不出所料,认为第二个对象“已被破坏”。

  • 相关阅读:
    JS日期选择器
    兼容多种浏览器的Ctrl+Enter提交兼容firefox、ie、opera
    在线播放器 在网页中插入MediaPlayer 兼容IE和FF的代码调试
    Android客户端开发即WebView组件的使用详解
    web开发时碰到的问题以及心得经验
    Select的OnChange()事件
    iPhone 浏览器开发之关键 UIWebView
    获取css样式表内样式的js函数currentStyle(IE),defaultView(FF)
    利用XMLHTTP无刷新添加数据之Get篇
    客户端表单通用验证checkForm(oForm) js版
  • 原文地址:https://www.cnblogs.com/liuhui0308/p/12588723.html
Copyright © 2011-2022 走看看