diff -r b6eebe7cf5ae Doc/library/hashlib.rst --- a/Doc/library/hashlib.rst Wed Mar 30 21:11:16 2016 +0300 +++ b/Doc/library/hashlib.rst Thu Mar 31 23:40:56 2016 +0300 @@ -176,6 +176,42 @@ 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. Available algorithms +are :func:`md5`, :func:`sha1`, :func:`sha224`, :func:`sha256`, :func:`sha384`, +and :func:`sha512`. Additional algorithms may also be available depending upon +the OpenSSL library that Python uses on your platform. + +When a ``file`` argument is given the hash is calculated on the file:: + + $ python -m hashlib md5 /bin/sh + $ d985d0ea551c1253c2305140c583d11f /bin/sh + +Multiple files can also be specified:: + + $ python -m hashlib md5 /bin/sh /bin/ls + $ d985d0ea551c1253c2305140c583d11f /bin/sh + 0a5cfeb3bb10e0971895f8899a64e816 /bin/ls + +You may use wildcards:: + + $ python -m hashlib sha1 /bin/mk* + $ 25bd1c99037c705f32b85016634b669de267bac4 /bin/mkdir + 8d68da857cb51b14d9e2832c9b35678bf01d9b91 /bin/mknod + 99c72e36f932b5af7ce83c9a28ae8e87233d4209 /bin/mktemp + +With no ``file``, or when ``file`` is ``-``, read standard input:: + + $ cat /bin/sh | python -m hashlib md5 + $ d985d0ea551c1253c2305140c583d11f - + Key derivation -------------- diff -r b6eebe7cf5ae Lib/hashlib.py --- a/Lib/hashlib.py Wed Mar 30 21:11:16 2016 +0300 +++ b/Lib/hashlib.py Thu Mar 31 23:40:56 2016 +0300 @@ -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,37 @@ # 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('algorithm', action='store', + choices=algorithms_available, + help='Specify algorithm to use for hash calculation.') + parser.add_argument('file', metavar='FILE', + default='-', nargs='*', + help="Calculate hash on FILE's list. With no FILE, or when FILE is -, read standard input.") + args = parser.parse_args() + for file_name in args.file: + if file_name == '-': + file_name = sys.stdin.fileno() + try: + with open(file_name, 'rb') as f: + hash_obj = new(args.algorithm) + # TODO: choose block size + block_size = 2**12 + while True: + data = f.read(block_size) + hash_obj.update(data) + if len(data) < block_size: + break + print('{} {}'.format(hash_obj.hexdigest(), file_name if isinstance(file_name, str) else '-')) + except OSError as e: + print("'{}': {}".format(e.filename, e.strerror)) + + +if __name__ == '__main__': + main() diff -r b6eebe7cf5ae Lib/test/test_hashlib.py --- a/Lib/test/test_hashlib.py Wed Mar 30 21:11:16 2016 +0300 +++ b/Lib/test/test_hashlib.py Thu Mar 31 23:40:56 2016 +0300 @@ -11,6 +11,8 @@ import itertools import os import sys +import tempfile +from collections import OrderedDict try: import threading except ImportError: @@ -18,7 +20,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 +528,63 @@ self._test_pbkdf2_hmac(c_hashlib.pbkdf2_hmac) +class TestMain(unittest.TestCase): + supported_algorithms = hashlib.algorithms_available + file_data = [("test_a", b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), + ("test_b", b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')] + + def setUp(self): + self.temp_dir_obj = tempfile.TemporaryDirectory() + self.temp_dir = self.temp_dir_obj.name + self.file_dict = OrderedDict() + for file_name, file_data in self.file_data: + full_path = os.path.join(self.temp_dir, file_name) + self.file_dict[full_path] = file_data + with open(full_path, 'wb') as f: + f.write(file_data) + + def tearDown(self): + self.temp_dir_obj.cleanup() + + def get_output(self, *args): + return script_helper.assert_python_ok('-m', 'hashlib', *args).out.rstrip() + + def expected_output(self, algorithm, file_name_data_list): + output = [] + for file_name, data in file_name_data_list: + output.append("{} {}".format( + hashlib.new(algorithm, data).hexdigest(), file_name)) + return "\n".join(output).encode("ascii") + + def test_calculate_file_hash(self): + for file_name in self.file_dict: + for algorithm in self.supported_algorithms: + self.assertEqual( + self.get_output(algorithm, file_name), + self.expected_output(algorithm, + [(file_name, self.file_dict[file_name])])) + + def test_calculate_hash_of_multiple_files(self): + for algorithm in self.supported_algorithms: + self.assertEqual(self.get_output(algorithm, *self.file_dict.keys()), + self.expected_output(algorithm, self.file_dict.items())) + + def test_calculate_hash_on_non_existing_file(self): + self.assertEqual(self.get_output("md5", "none_existing_path"), + b"'none_existing_path': No such file or directory" ) + + def test_calculate_hash_on_directory(self): + self.assertEqual(self.get_output("md5", self.temp_dir), + "'{}': Is a directory".format(self.temp_dir).encode("ascii")) + + def test_calculate_hash_from_stdin(self): + data = list(self.file_dict.values())[0] + for algorithm in self.supported_algorithms: + with script_helper.spawn_python('-m', 'hashlib', algorithm) as proc: + out, err = proc.communicate(data) + self.assertEqual(out.rstrip(), self.expected_output(algorithm, [("-", data)])) + self.assertIsNone(err) + + if __name__ == "__main__": unittest.main()