diff -r be3874cf87f3 Doc/library/hashlib.rst --- a/Doc/library/hashlib.rst Sat Mar 05 14:05:45 2016 +0200 +++ b/Doc/library/hashlib.rst Sat Mar 05 23:43:36 2016 +0200 @@ -176,6 +176,35 @@ compute the digests of data sharing a common initial substring. +.. _hashlib-commandline: + +Command Line Interface +---------------------- + +.. versionadded:: 3.6 + +:mod:`hashlib` can also be invoked directly using the :option:`-m` +switch of the interpreter with a ``algorithm`` argument. + +When a ``file`` argument is given the hash is calculated on the file:: + + $ python -m hashlib md5 /bin/sh + $ d985d0ea551c1253c2305140c583d11f + +With no ``file``, or when ``file`` is -, read standard input:: + + $ cat /bin/sh | python -m hashlib md5 + $ d985d0ea551c1253c2305140c583d11f + +By default, we read up to 1024 bytes at a time from the given file. +The option :option:`-b` can be used to change the reading block size. +For example, the following command causes the the file to be read up +to 512 bytes at a time:: + + $ python -m hashlib -b 512 sha1 /bin/sh + $ 647437c3d7543c7c8d381903834c9ef42eb4cf69 + + Key derivation -------------- diff -r be3874cf87f3 Lib/hashlib.py --- a/Lib/hashlib.py Sat Mar 05 14:05:45 2016 +0200 +++ b/Lib/hashlib.py Sat Mar 05 23:43:36 2016 +0200 @@ -1,3 +1,4 @@ +#! /usr/bin/env python3 #. Copyright (C) 2005-2010 Gregory P. Smith (greg@krypto.org) # Licensed to PSF under a Contributor Agreement. # @@ -215,3 +216,31 @@ # Cleanup locals() del __always_supported, __func_name, __get_hash del __py_new, __hash_new, __get_openssl_constructor + + +# Usable as a script... +def main(): + import argparse + import sys + parser = argparse.ArgumentParser() + parser.add_argument('-b', metavar='BLOCK_SIZE', type=int, + dest="block_size", default=2**10, + help='Read up to BLOCK_SIZE bytes at a time.') + parser.add_argument('algorithm', action='store', type=str, + choices=('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'), + help='Specify algorithm to use for hash calculation.') + parser.add_argument('file', metavar='FILE', type=argparse.FileType("rb"), + default=sys.stdin.buffer, nargs='?', + help='Calculate hash on given file. With no FILE, or when FILE is -, read standard input.') + args = parser.parse_args() + hash_obj = globals()[args.algorithm]() + while True: + data = args.file.read(args.block_size) + if not data: + break + hash_obj.update(data) + print(hash_obj.hexdigest()) + + +if __name__ == '__main__': + main() diff -r be3874cf87f3 Lib/test/test_hashlib.py --- a/Lib/test/test_hashlib.py Sat Mar 05 14:05:45 2016 +0200 +++ b/Lib/test/test_hashlib.py Sat Mar 05 23:43:36 2016 +0200 @@ -18,7 +18,7 @@ import unittest import warnings from test import support -from test.support import _4G, bigmemtest, import_fresh_module +from test.support import _4G, bigmemtest, import_fresh_module, script_helper # Were we compiled --with-pydebug or with #define Py_DEBUG? COMPILED_WITH_PYDEBUG = hasattr(sys, 'gettotalrefcount') @@ -526,5 +526,41 @@ self._test_pbkdf2_hmac(c_hashlib.pbkdf2_hmac) +class TestMain(unittest.TestCase): + supported_algorithms = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512') + data = b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + + def setUp(self): + with open(support.TESTFN, 'wb') as fp: + fp.write(self.data) + + def tearDown(self): + if os.path.exists(support.TESTFN): + os.unlink(support.TESTFN) + + def get_output(self, *args): + return script_helper.assert_python_ok('-m', 'hashlib', *args).out + + def test_calculate_file_hash(self): + for algorithm in self.supported_algorithms: + output = self.get_output(algorithm, support.TESTFN) + calculated = getattr(hashlib, algorithm)(self.data).hexdigest().encode("ascii") + self.assertEqual(output.rstrip(), calculated) + + def test_calculate_hash_from_stdin(self): + for algorithm in self.supported_algorithms: + with script_helper.spawn_python('-m', 'hashlib', algorithm) as proc: + out, err = proc.communicate(self.data) + calculated = getattr(hashlib, algorithm)(self.data).hexdigest().encode("ascii") + self.assertEqual(out.rstrip(), calculated) + self.assertIsNone(err) + + def test_block_size(self): + for block_size in [2, 2**2, 2**4, 2**8]: + output = self.get_output("-b", str(block_size), "md5", support.TESTFN) + calculated = getattr(hashlib, "md5")(self.data).hexdigest().encode("ascii") + self.assertEqual(output.rstrip(), calculated) + + if __name__ == "__main__": unittest.main()