NistBeacon: NIST randomness beacon with python

https://travis-ci.org/urda/nistbeacon.svg?branch=master https://codecov.io/gh/urda/nistbeacon/branch/master/graph/badge.svg https://img.shields.io/pypi/l/nistbeacon.svg https://img.shields.io/pypi/pyversions/nistbeacon.svg

Important Notice!

WARNING: DO NOT USE BEACON GENERATED VALUES AS SECRET CRYPTOGRAPHIC KEYS.

Remember, these random numbers are not only available over the public internet, but are kept on “record” and can be accessed at anytime with just a timestamp. Make sure you understand the implications of using the NIST randomness beacon in your projects.

Contents

Installation

Prerequisites

A required library pycryptodome is used with nistbeacon.

Ubuntu, and other Linux-based users should have python3-dev installed.

apt-get install python3-dev

Installing nistbeacon

To install the beacon library, simply use pip:

pip install nistbeacon

Beacon Usage

It is easy to use the beacon. Most queries are performed through NistBeacon which produces NistBeaconValue objects.

Sample Code

from nistbeacon import NistBeacon

# In the examples below I will be using 1447873020
# as my <timestamp> when required

# Current Record (or next closest)
# https://beacon.nist.gov/rest/record/<timestamp>
record = NistBeacon.get_record(1447873020)

# Previous Record
# https://beacon.nist.gov/rest/record/previous/<timestamp>
prev_record = NistBeacon.get_previous(1447873020)

# Next Record
# https://beacon.nist.gov/rest/record/next/<timestamp>
next_record = NistBeacon.get_next(1447873020)

# First Record
# https://beacon.nist.gov/rest/record/1378395540
first_record = NistBeacon.get_first_record(download=True)

# Last Record
# https://beacon.nist.gov/rest/record/last
last_record = NistBeacon.get_last_record()

# Verify the record and the record chain
record_chain_result = NistBeacon.chain_check(1447873020)

Beacon Methods

chain_check

@classmethod
def chain_check(cls, timestamp: int) -> bool:
    """
    Given a record timestamp, verify the chain integrity.

    :param timestamp: UNIX time / POSIX time / Epoch time
    :return: 'True' if the timestamp fits the chain. 'False' otherwise.
    """

get_first_record

@classmethod
def get_first_record(
        cls,
        download: bool=False
) -> NistBeaconValue:
    """
    Get the first (oldest) record available. Since the first record
    IS a known value in the system we can load it from constants.

    :param download: 'True' will always reach out to NIST to get the
                     first record. 'False' returns a local copy.
    :return: The first beacon value. 'None' otherwise.
    """

get_last_record

@classmethod
def get_last_record(cls) -> NistBeaconValue:
    """
    Get the last (newest) record available.

    :return: The last beacon value. 'None' otherwise.
    """

get_next

@classmethod
def get_next(cls, timestamp: int) -> NistBeaconValue:
    """
    Given a record timestamp, get the next record available.

    :param timestamp: UNIX time / POSIX time / Epoch time
    :return: The next beacon value if available. 'None' otherwise.
    """

get_previous

@classmethod
def get_previous(cls, timestamp: int) -> NistBeaconValue:
    """
    Given a record timestamp, get the previous record available.

    :param timestamp: UNIX time / POSIX time / Epoch time
    :return: The previous beacon value if available. 'None; otherwise
    """

get_record

@classmethod
def get_record(cls, timestamp: int) -> NistBeaconValue:
    """
    Get a specific record (or next closest)

    :param timestamp: UNIX time / POSIX time / Epoch time
    :return: The requested beacon value if available. 'None' otherwise.
    """

Beacon Value

The NistBeaconValue objects act as basic python objects. As one would expect, there are a number of properties and methods available on it.

Instance Details

"""
:param version:
    Reported NIST randomness beacon version

:param frequency:
    The time interval, in seconds, between expected records

:param timestamp:
    The time the seed value was generated as the number of
    seconds since January 1, 1970

:param seed_value:
    A seed value represented as a 64 byte (512-bit) hex string
    value

:param previous_output_value:
    The SHA-512 hash value for the previous record - 64 byte hex
    string

:param signature_value:
    A digital signature (RSA) computed over (in order): version,
    frequency, timeStamp, seedValue, previousHashValue, statusCode

    Note: Except for version, the hash is on the byte
    representations and not the string representations of the data
    values

:param output_value:
    The SHA-512 hash of the signatureValue as a 64 byte hex string

:param status_code:
    The status code value:
        0 - Chain intact, values all good
        1 - Start of a new chain of values, previous hash value
            will be all zeroes
        2 - Time between values is greater than the frequency, but
            the chain is still intact
"""

Properties

frequency

@property
def frequency(self) -> int:
    """
    :return: The time interval, in seconds, between expected records
    """

json

@property
def json(self) -> str:
    """
    :return: The JSON representation of the beacon, as a string
    """

output_value

@property
def output_value(self) -> str:
    """
    :return: The SHA-512 hash of the signatureValue as a 64 byte hex string
    """

previous_output_value

@property
def previous_output_value(self) -> str:
    """
    :return:
        The SHA-512 hash value for the previous record - 64 byte hex
        string
    """

pseudo_random

@property
def pseudo_random(self) -> Random:
    """
    :return:
        A python `random.Random` object that has been seeded with
        the value's `output_value`. This is a pseudo-random
        number generator
    """

seed_value

@property
def seed_value(self) -> str:
    """
    :return:
        A seed value represented as a 64 byte (512-bit) hex string
        value
    """

signature_value

@property
def signature_value(self) -> str:
    """
    :return:
        A digital signature (RSA) computed over (in order): version,
        frequency, timeStamp, seedValue, previousHashValue, statusCode

        Note: Except for version, the hash is on the byte
        representations and not the string representations of the data
        values
    """

status_code

@property
def status_code(self) -> str:
    """
    :return:
        The status code value:
            0 - Chain intact, values all good
            1 - Start of a new chain of values, previous hash value
                will be all zeroes
            2 - Time between values is greater than the frequency, but
                the chain is still intact
    """

timestamp

@property
def timestamp(self) -> int:
    """
    :return:
        The time the seed value was generated as the number of
        seconds since January 1, 1970
    """

valid_signature

@property
def valid_signature(self) -> bool:
    """
    Shows the result of signature verification

    First, required records (version, frequency, timestamp,
    seed_value, previous_output_value) are packed together to form
    a message. This message is then checked against the record's reported
    signature field WITH the known NIST public key.

    Second, the signature value is independently ran through a SHA512
    hash. The result of this operation SHOULD equal the record's reported
    output_value field.

    As long as the result of the 'First' step and'ed with the 'Second'
    step, the record is considered valid.

    :return: 'True' if this record is valid. 'False' otherwise
    """

version

@property
def version(self) -> str:
    """
    :return: Reported NIST randomness beacon version
    """

xml

@property
def xml(self) -> str:
    """
    :return: The XML representation of the beacon, as a string
    """

Methods

from_json

@classmethod
def from_json(cls, input_json: str):
    """
    Convert a string of JSON which represents a NIST randomness beacon
    value into a 'NistBeaconValue' object.

    :param input_json: JSON to build a 'Nist RandomnessBeaconValue' from
    :return: A 'NistBeaconValue' object, 'None' otherwise
    """

from_xml

@classmethod
def from_xml(cls, input_xml: str):
    """
    Convert a string of XML which represents a NIST Randomness Beacon value
    into a 'NistBeaconValue' object.

    :param input_xml: XML to build a 'NistBeaconValue' from
    :return: A 'NistBeaconValue' object, 'None' otherwise

Project Information

Project Health

Branch Build Status Coverage Status
Master MasterBuild MasterCoverage
Develop DevelopBuild DevelopCoverage

Contributing

Please refer to the CONTRIBUTING document on GitHub

CHANGELOG

v0.9.4

  • Project Changes
  • Switched to pycryptodome from pycrypto.
  • Started locking versions for requirements.txt.
  • Created a sphinx based documentation site.
  • Internal Changes
  • Synced LICENSE file to use boilerplate notice.
  • Removed nist_records dependency on NistBeaconValue
  • Removed local_record_db. Most records are stored in json now.

v0.9.3

v0.9.2

  • Internal Changes
  • LICENSE
    • Updated for 2016.
  • NistBeacon
    • get_first_record now defaults to downloading the first record.
  • NistBeaconCrypto
    • Now computes the struct and other values for the SHA512Hash.
  • NistBeaconValue
    • Pushed struct and signature hash building into NistBeaconCrypto.
  • Project Changes
  • Coverage Tool
    • Switched from Coveralls to Codecov.

v0.9.1

  • Internal Changes
  • NistBeacon
    • NIST_BASE_URL renamed to _NIST_API_URL, to clarify that the value should NOT be altered under normal circumstances.
  • NistBeaconCrypto
    • New helper class for signature checking of NistBeaconValue objects. This is NOT a class designed for general use!
  • NistBeaconValue
    • Added a helper class NistBeaconCrypto to handle SHA512 generation and signature checking. This means that NistBeaconCrypto needs to be the only reference for key import and signature checking. All other NistBeaconValue do not have to generate the full RSA objects.
    • Started using the now existing xmlns property directly from NIST.

v0.9.0

  • Features
  • NistBeaconValue
    • Added a pseudo_random property. Returns a random.Random object that has been seeded with the output_value for a given NistBeaconValue.

v0.8.3

  • Internal Changes
  • NistBeaconValue
    • Creating a beacon value will store the JSON, XML representations once. These values do not have to be computed on each json or xml property access now as before.
  • Project Changes (for Developers)
  • pylint has been added to the project and build process.

v0.8.2

  • Bug Fixes:
  • NistBeaconValue
    • Reported issue where a xmlns value on record ended up breaking XML loading. Reported on GitHub. Since this is just a bug fix release. This xmlns value will not show up if one was to use the xml value from the NistBeaconValue object.

v0.8.1

  • Minor documentation changes

v0.8.0

  • Features
  • NistBeaconValue
    • Added json and xml as properties (replaces to_json() and to_xml())

v0.7.0

  • Name changes
  • Changed from py_nist_beacon to nistbeacon
  • Changed from NistRandomnessBeacon to NistBeacon
  • Changed from NistRandomnessBeaconValue to NistBeaconValue

v0.6.0

  • Features
  • NistRandomnessBeacon
    • Added a get_first_record method. An optional boolean flag named download allows the caller to either use the local first record object, or to download the first record directly from the NIST beacon.

v0.5.2

  • Added a section on installation.
  • Updated CONTRIBUTING
  • Re-do PHONY targets in Makefile
  • Update travis build steps to include 3.5-dev and nightly

v0.5.1

  • Badges made to point to their release branches

v0.5.0

  • General
  • Lots of documentation added through docstrings! 📝
  • NistRandomnessBeacon
  • The beacon now understands how to check the chain. Using the chain_check method on the beacon with a given timestamp value the NIST Randomness Beacon chain can be verified for integrity purposes. 🔗
@classmethod
def chain_check(cls, timestamp: int) -> bool:
    """
    Given a record timestamp, verify the chain integrity.

    :param timestamp: UNIX time / POSIX time / Epoch time
    :return: 'True' if the timestamp fits the chain. 'False' otherwise.
    """
  • NistRandomnessBeaconValue
  • ⚠️ All properties of the beacon have been placed behind @property decorators to minimize possible manipulation
  • ⚠️ verify_signature has been removed from beacon values. Replaced with ``valid_signature``
  • Introduced valid_signature as a bool property. Replaces ``verify_signature``

v0.4.0

  • Added a verify_signature to NistRandomnessBeaconValue objects. This method returns a True or False after verifying the provided record. The record is verified using two steps:
  • First, using a combination of input data of the record, a simple message is packed to create a message. That message is then used in combination with the record’s reported signature_value and the known NIST Beacon X.509 certificate. This certificate is available for download, but is baked into the application as follows:
    • Original CER file as a string: nist_beacon_constants.py - NIST_CER_FILE
    • Original Public Key as a string: nist_beacon_constants.py - NIST_RSA_KEY
    • Hard copy of beacon.cer is provided at the root of the project
  • Second, the signature_value is ran through a sha512 hash to confirm the output_value is correct on the record.
  • If either of the steps are found to be invalid, verify_signature will return a False result.

v0.3.0

  • Added to_xml, to_json, and from_json methods on beacon values

v0.2.0

  • Initial PyPI release package