diff --git a/go.mod b/go.mod index aed4efd5..456fbefb 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,6 @@ module github.com/go-python/gpython -require github.com/peterh/liner v1.1.0 +require ( + github.com/peterh/liner v1.1.0 + golang.org/x/crypto v0.0.0-20180830192347-182538f80094 // indirect +) diff --git a/go.sum b/go.sum index 324d4e14..61622022 100644 --- a/go.sum +++ b/go.sum @@ -2,3 +2,5 @@ github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8Bz github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/peterh/liner v1.1.0 h1:f+aAedNJA6uk7+6rXsYBnhdo4Xux7ESLe+kcuVUF5os= github.com/peterh/liner v1.1.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +golang.org/x/crypto v0.0.0-20180830192347-182538f80094 h1:rVTAlhYa4+lCfNxmAIEOGQRoD23UqP72M3+rSWVGDTg= +golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= diff --git a/hashlib/hashlib.go b/hashlib/hashlib.go new file mode 100644 index 00000000..6d226e51 --- /dev/null +++ b/hashlib/hashlib.go @@ -0,0 +1,163 @@ +// Copyright 2018 The go-python Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* Hashlib module -- standard C hashlib library functions */ +package hashlib + +import ( + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "hash" + + "golang.org/x/crypto/sha3" + + "github.com/go-python/gpython/py" +) + +/* Implements the HMAC algorithm as described by RFC 2104. */ + +const hashlib_new_doc = `new(name, data=b'') - Return a new hashing object using the named algorithm; +optionally initialized with data (which must be bytes).` + +func hashlib_new(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { + var on py.Object + var os py.Object = py.Bytes(nil) + + kwlist := []string{"name", "data"} + + err := py.ParseTupleAndKeywords(args, kwargs, "s|y:new", kwlist, &on, &os) + if err != nil { + return nil, err + } + + name, err := py.StrAsString(on) + if err != nil { + return nil, err + } + + data, err := py.BytesFromObject(os) + if err != nil { + return nil, err + } + + var hasher hash.Hash + switch name { + case "md5": + hasher = md5.New() + case "sha1": + hasher = sha1.New() + case "sha224": + hasher = sha256.New224() + case "sha256": + hasher = sha256.New() + case "sha384": + hasher = sha512.New384() + case "sha512": + hasher = sha512.New() + case "sha3_256": + hasher = sha3.New256() + case "sha3_384": + hasher = sha3.New384() + case "sha3_512": + hasher = sha3.New512() + default: + return nil, py.ExceptionNewf(py.ValueError, "unsupported hash type "+name) + } + + _, err = hasher.Write(data) + return py.NewHash(name, hasher), err +} + +func hashlib_md5(self py.Object, args py.Tuple) (py.Object, error) { + return hashlib_new(self, append([]py.Object{py.String("md5")}, args...), nil) +} + +func hashlib_sha1(self py.Object, args py.Tuple) (py.Object, error) { + return hashlib_new(self, append([]py.Object{py.String("sha1")}, args...), nil) +} + +func hashlib_sha224(self py.Object, args py.Tuple) (py.Object, error) { + return hashlib_new(self, append([]py.Object{py.String("sha224")}, args...), nil) +} + +func hashlib_sha256(self py.Object, args py.Tuple) (py.Object, error) { + return hashlib_new(self, append([]py.Object{py.String("sha256")}, args...), nil) +} + +func hashlib_sha384(self py.Object, args py.Tuple) (py.Object, error) { + return hashlib_new(self, append([]py.Object{py.String("sha384")}, args...), nil) +} + +func hashlib_sha512(self py.Object, args py.Tuple) (py.Object, error) { + return hashlib_new(self, append([]py.Object{py.String("sha512")}, args...), nil) +} + +func hashlib_sha3_256(self py.Object, args py.Tuple) (py.Object, error) { + return hashlib_new(self, append([]py.Object{py.String("sha3_256")}, args...), nil) +} + +func hashlib_sha3_384(self py.Object, args py.Tuple) (py.Object, error) { + return hashlib_new(self, append([]py.Object{py.String("sha3_384")}, args...), nil) +} + +func hashlib_sha3_512(self py.Object, args py.Tuple) (py.Object, error) { + return hashlib_new(self, append([]py.Object{py.String("sha3_512")}, args...), nil) +} + +const hashlib_doc = `hashlib module - A common interface to many hash functions. +new(name, data=b'') - returns a new hash object implementing the + given hash function; initializing the hash + using the given binary data. +Named constructor functions are also available, these are faster +than using new(name): +md5(), sha1(), sha224(), sha256(), sha384(), and sha512() +More algorithms may be available on your platform but the above are guaranteed +to exist. See the algorithms_guaranteed and algorithms_available attributes +to find out what algorithm names can be passed to new(). +NOTE: If you want the adler32 or crc32 hash functions they are available in +the zlib module. +Choose your hash function wisely. Some have known collision weaknesses. +sha384 and sha512 will be slow on 32 bit platforms. +Hash objects have these methods: + - update(arg): Update the hash object with the bytes in arg. Repeated calls + are equivalent to a single call with the concatenation of all + the arguments. + - digest(): Return the digest of the bytes passed to the update() method + so far. + - hexdigest(): Like digest() except the digest is returned as a unicode + object of double length, containing only hexadecimal digits. + - copy(): Return a copy (clone) of the hash object. This can be used to + efficiently compute the digests of strings that share a common + initial substring. +For example, to obtain the digest of the string 'Nobody inspects the +spammish repetition': + >>> import hashlib + >>> m = hashlib.md5() + >>> m.update(b"Nobody inspects") + >>> m.update(b" the spammish repetition") + >>> m.digest() + b'\\xbbd\\x9c\\x83\\xdd\\x1e\\xa5\\xc9\\xd9\\xde\\xc9\\xa1\\x8d\\xf0\\xff\\xe9' +More condensed: + >>> hashlib.sha224(b"Nobody inspects the spammish repetition").hexdigest() + 'a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2' +` + +// Initialise the module +func init() { + methods := []*py.Method{ + py.MustNewMethod("new", hashlib_new, 0, hashlib_new_doc), + py.MustNewMethod("md5", hashlib_md5, 0, "Returns a md5 hash object; optionally initialized with a string"), + py.MustNewMethod("sha1", hashlib_sha1, 0, "Returns a sha1 hash object; optionally initialized with a string"), + py.MustNewMethod("sha224", hashlib_sha224, 0, "Returns a sha224 hash object; optionally initialized with a string"), + py.MustNewMethod("sha256", hashlib_sha256, 0, "Returns a sha256 hash object; optionally initialized with a string"), + py.MustNewMethod("sha384", hashlib_sha384, 0, "Returns a sha384 hash object; optionally initialized with a string"), + py.MustNewMethod("sha512", hashlib_sha512, 0, "Returns a sha512 hash object; optionally initialized with a string"), + py.MustNewMethod("sha3_256", hashlib_sha3_256, 0, "Return a new SHA3 hash object with a hashbit length of 32 bytes."), + py.MustNewMethod("sha3_384", hashlib_sha3_384, 0, "Return a new SHA3 hash object with a hashbit length of 48 bytes."), + py.MustNewMethod("sha3_512", hashlib_sha3_512, 0, "Return a new SHA3 hash object with a hashbit length of 64 bytes."), + } + py.NewModule("hashlib", hashlib_doc, methods, nil) +} diff --git a/hashlib/hashlib_test.go b/hashlib/hashlib_test.go new file mode 100644 index 00000000..1050ca1f --- /dev/null +++ b/hashlib/hashlib_test.go @@ -0,0 +1,15 @@ +// Copyright 2018 The go-python Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package hashlib_test + +import ( + "testing" + + "github.com/go-python/gpython/pytest" +) + +func TestVm(t *testing.T) { + pytest.RunTests(t, "tests") +} diff --git a/hashlib/tests/hashlib.py b/hashlib/tests/hashlib.py new file mode 100644 index 00000000..86b91015 --- /dev/null +++ b/hashlib/tests/hashlib.py @@ -0,0 +1,52 @@ +# Copyright 2018 The go-python Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# FIXME Convert tests from cpython/Lib/test/test_hashlib.py + +import hashlib +from libtest import * + +def ftest(name, got, want): + what = '%s got %r, want %r' % (name, got, want) + assert got == want, what + +doc="MD5" +ftest('new_md5_0', hashlib.new('md5', b'').hexdigest(), 'd41d8cd98f00b204e9800998ecf8427e') +ftest('new_md5_1', hashlib.new('md5', b'').digest(), b'\xd4\x1d\x8c\xd9\x8f\x00\xb2\x04\xe9\x80\t\x98\xec\xf8B~') +ftest('new_md5_2', hashlib.new('md5', b'abc').hexdigest(), '900150983cd24fb0d6963f7d28e17f72') +ftest('new_md5_3', hashlib.new('md5', b'abc').digest(), b'\x90\x01P\x98<\xd2O\xb0\xd6\x96?}(\xe1\x7fr') +ftest('new_md5_4', + hashlib.new('md5', b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789').hexdigest(), + 'd174ab98d277d9f5a5611c2c9f419d9f') +assertRaises(TypeError, hashlib.new, 'md5', 'abc') + +ftest('md5_0', hashlib.md5().hexdigest(), 'd41d8cd98f00b204e9800998ecf8427e') +ftest('md5_1', hashlib.md5().digest(), b'\xd4\x1d\x8c\xd9\x8f\x00\xb2\x04\xe9\x80\t\x98\xec\xf8B~') +ftest('md5_2', hashlib.md5(b'abc').hexdigest(), '900150983cd24fb0d6963f7d28e17f72') +ftest('md5_3', hashlib.md5(b'abc').digest(), b'\x90\x01P\x98<\xd2O\xb0\xd6\x96?}(\xe1\x7fr') +ftest('md5_4', + hashlib.md5(b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789').hexdigest(), + 'd174ab98d277d9f5a5611c2c9f419d9f') +assertRaises(TypeError, hashlib.md5, 'abc') + +doc="SHA1" +ftest('new_sha1_0', hashlib.new('sha1', b'').hexdigest(), 'da39a3ee5e6b4b0d3255bfef95601890afd80709') +ftest('new_sha1_1', hashlib.new('sha1', b'').digest(), b'\xda9\xa3\xee^kK\r2U\xbf\xef\x95`\x18\x90\xaf\xd8\x07\t') +ftest('new_sha1_2', hashlib.new('sha1', b'abc').hexdigest(), 'a9993e364706816aba3e25717850c26c9cd0d89d') +ftest('new_sha1_3', hashlib.new('sha1', b'abc').digest(), b'\xa9\x99>6G\x06\x81j\xba>%qxP\xc2l\x9c\xd0\xd8\x9d') +ftest('new_sha1_4', + hashlib.new('sha1', b'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq').hexdigest(), + '84983e441c3bd26ebaae4aa1f95129e5e54670f1') +assertRaises(TypeError, hashlib.new, 'sha1', 'abc') + +ftest('sha1_0', hashlib.sha1(b'').hexdigest(), 'da39a3ee5e6b4b0d3255bfef95601890afd80709') +ftest('sha1_1', hashlib.sha1(b'').digest(), b'\xda9\xa3\xee^kK\r2U\xbf\xef\x95`\x18\x90\xaf\xd8\x07\t') +ftest('sha1_2', hashlib.sha1(b'abc').hexdigest(), 'a9993e364706816aba3e25717850c26c9cd0d89d') +ftest('sha1_3', hashlib.sha1(b'abc').digest(), b'\xa9\x99>6G\x06\x81j\xba>%qxP\xc2l\x9c\xd0\xd8\x9d') +ftest('sha1_4', + hashlib.sha1(b'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq').hexdigest(), + '84983e441c3bd26ebaae4aa1f95129e5e54670f1') +assertRaises(TypeError, hashlib.sha1, 'abc') + +doc="finished" diff --git a/hashlib/tests/libtest.py b/hashlib/tests/libtest.py new file mode 100644 index 00000000..d3894f1a --- /dev/null +++ b/hashlib/tests/libtest.py @@ -0,0 +1,41 @@ +# Copyright 2018 The go-python Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +""" +Simple test harness +""" + +def assertRaises(expecting, fn, *args, **kwargs): + """Check the exception was raised - don't check the text""" + try: + fn(*args, **kwargs) + except expecting as e: + pass + else: + assert False, "%s not raised" % (expecting,) + +def assertRaisesText(expecting, text, fn, *args, **kwargs): + """Check the exception with text in is raised""" + try: + fn(*args, **kwargs) + except expecting as e: + assert text in e.args[0], "'%s' not found in '%s'" % (text, e.args[0]) + else: + assert False, "%s not raised" % (expecting,) + +def assertTrue(x): + """assert x is True""" + assert x + +def assertFalse(x): + """assert x is False""" + assert not x + +def assertEqual(x, y): + """assert x == y""" + assert x == y + +def fail(x): + """Fails with error message""" + assert False, x diff --git a/main.go b/main.go index 66e493f7..3f90145a 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( "strings" "github.com/go-python/gpython/compile" + _ "github.com/go-python/gpython/hashlib" "github.com/go-python/gpython/marshal" _ "github.com/go-python/gpython/math" "github.com/go-python/gpython/py" diff --git a/py/args.go b/py/args.go index d32c319e..8e828552 100644 --- a/py/args.go +++ b/py/args.go @@ -471,6 +471,11 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist default: return ExceptionNewf(TypeError, "%s() argument %d must be float, not %s", name, i+1, arg.Type().Name) } + case "y": + if _, ok := arg.(Bytes); !ok { + return ExceptionNewf(TypeError, "%s() argument %d must be byte-like object, not %s", name, i+1, arg.Type().Name) + } + *result = arg default: return ExceptionNewf(TypeError, "Unknown/Unimplemented format character %q in ParseTupleAndKeywords called from %s", op, name) diff --git a/py/hash.go b/py/hash.go new file mode 100644 index 00000000..0b39996e --- /dev/null +++ b/py/hash.go @@ -0,0 +1,127 @@ +// Copyright 2018 The go-python Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Hash objects + +package py + +import ( + "bytes" + "fmt" + "hash" +) + +var HashType = ObjectType.NewType("hash", "a hash object implementing the given hash function", nil, nil) + +type Hash struct { + Name string + Hasher hash.Hash +} + +// Type of this Hash object +func (h Hash) Type() *Type { + return HashType +} + +// NewHash defines a new hash +func NewHash(name string, h hash.Hash) *Hash { + return &Hash{name, h} +} + +func (h *Hash) M__str__() (Object, error) { + return h.M__repr__() +} + +func (h *Hash) M__repr__() (Object, error) { + return String(fmt.Sprintf("<%s HASH object @ %v>", h.Name, h)), nil +} + +func (h Hash) M__eq__(other Object) (Object, error) { + b, ok := other.(Hash) + if !ok { + return NotImplemented, nil + } + return NewBool(bytes.Equal(h.Hasher.Sum(nil), b.Hasher.Sum(nil))), nil +} + +func (h Hash) M__ne__(other Object) (Object, error) { + b, ok := other.(Hash) + if !ok { + return NotImplemented, nil + } + return NewBool(!bytes.Equal(h.Hasher.Sum(nil), b.Hasher.Sum(nil))), nil +} + +func (h Hash) M__gt__(other Object) (Object, error) { + b, ok := other.(Hash) + if !ok { + return NotImplemented, nil + } + return NewBool(bytes.Compare(h.Hasher.Sum(nil), b.Hasher.Sum(nil)) > 0), nil +} + +func (h Hash) M__lt__(other Object) (Object, error) { + b, ok := other.(Hash) + if !ok { + return NotImplemented, nil + } + return NewBool(bytes.Compare(h.Hasher.Sum(nil), b.Hasher.Sum(nil)) < 0), nil +} + +func (h Hash) M__ge__(other Object) (Object, error) { + b, ok := other.(Hash) + if !ok { + return NotImplemented, nil + } + return NewBool(bytes.Compare(h.Hasher.Sum(nil), b.Hasher.Sum(nil)) >= 0), nil +} + +func (h Hash) M__le__(other Object) (Object, error) { + b, ok := other.(Hash) + if !ok { + return NotImplemented, nil + } + return NewBool(bytes.Compare(h.Hasher.Sum(nil), b.Hasher.Sum(nil)) <= 0), nil +} + +func init() { + HashType.Dict["update"] = MustNewMethod("update", func(self Object, arg Object) (Object, error) { + data, err := BytesFromObject(arg) + if err != nil { + return self, err + } + _, err = self.(*Hash).Hasher.Write(data) + return self, err + }, 0, "update(arg) -> Update the hash object with the object arg, which must be interpretable as a buffer of bytes. Repeated calls are equivalent to a single call with the concatenation of all the arguments: m.update(a); m.update(b) is equivalent to m.update(a+b).") + + HashType.Dict["digest"] = MustNewMethod("digest", func(self Object) (Object, error) { + return Bytes(self.(*Hash).Hasher.Sum(nil)), nil + }, 0, "digest() -> Return the digest of the data passed to the update() method so far. This is a bytes object of size digest_size which may contain bytes in the whole range from 0 to 255.") + + HashType.Dict["hexdigest"] = MustNewMethod("hexdigest", func(self Object) (Object, error) { + return String(fmt.Sprintf("%x", self.(*Hash).Hasher.Sum(nil))), nil + }, 0, "hexdigest() -> Like digest() except the digest is returned as a string object of double length, containing only hexadecimal digits. This may be used to exchange the value safely in email or other non-binary environments.") + + // FIXME find a way to implement copy() method + + HashType.Dict["name"] = &Property{ + Fget: func(self Object) (Object, error) { + return String(self.(*Hash).Name), nil + }, + } + + HashType.Dict["block_size"] = &Property{ + Fget: func(self Object) (Object, error) { + return Int(self.(*Hash).Hasher.BlockSize()), nil + }, + } + + HashType.Dict["digest_size"] = &Property{ + Fget: func(self Object) (Object, error) { + return Int(self.(*Hash).Hasher.Size()), nil + }, + } +} + +var _ richComparison = new(Hash)