Skip to content

Commit

Permalink
Merge 6e5555d into 92ae06b
Browse files Browse the repository at this point in the history
  • Loading branch information
heronhaye committed Dec 4, 2018
2 parents 92ae06b + 6e5555d commit ee0c764
Show file tree
Hide file tree
Showing 18 changed files with 773 additions and 103 deletions.
8 changes: 3 additions & 5 deletions .travis.yml
@@ -1,17 +1,15 @@
language:
python
python:
- 2.6
- 2.7
- 3.3
- 3.6
install:
- pip install -e .
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi
- pip install -r requirements.txt
script:
nosetests --verbose
notifications:
email:
- filippo.valsorda@gmail.com
- max@keybase.io
after_success:
- pip install coveralls
- coverage run --source=triplesec setup.py -q nosetests
Expand Down
19 changes: 16 additions & 3 deletions README.rst
Expand Up @@ -74,11 +74,18 @@ Here is the help::
encoded; hex encode all output
-k KEY, --key KEY the TripleSec key; if not specified will check the
TRIPLESEC_KEY env variable, then prompt the user for it
--compatibility Use Keccak instead of SHA3 for the second MAC and reverse
endianness of Salsa20 in version 1. Only effective in
versions before 4.

API
---
Changes in 0.5
-----------------------
For message authentication, the Triplesec spec uses the Keccak SHA3 proposal function for versions 1 through 3, but for some time, this library used the standardized SHA3-512 function instead. Thus, by default, the Python implementation for versions 1 through 3 is incompatible with the JavaScript and Golang implementations.
From version 4 and going forward, the spec will use only the standardized SHA3-512 function (provided, for example, by `hashlib` in Python), and the Python, JavaScript, and Golang implementations will be compatible.

Sphinx documentation coming soon.
If you would like to use Keccak with versions 1 through 3 (and thus achieve compatibility with the Node and Go packages), you can pass in `compatibility=True` to `encrypt` and `decrypt`, or on the commandline as detailed in the Example section.

Additionally, invocations that do not specify a version will now use version 4 by default, which is incompatible with previous versions.

Example
-------
Expand All @@ -93,3 +100,9 @@ IT'S A YELLOW SUBMARINE
>>> x = T.encrypt(b"IT'S A YELLOW SUBMARINE")
>>> print(T.decrypt(x).decode())
IT'S A YELLOW SUBMARINE

# Use --compatibility in the command line to decrypt version 1 through 3 messages made with the Node or Go packages.
$ TRIPLESEC_KEY=abc triplesec dec 1c94d7de000000031355e46727ab2f1a1575a605e4aa5012dcf0e13e55891a4167b10a0f5c173a2e6c6cbb5718f3f7021005f2501b8b5b674bed2553687404aae7aed32d4e9a7bb456dbef209786ee14d974e7899a3d8bacfb7f6705f4abeb307047b1360fa2e5721e5e485361d3a59f426af89d6170fd67feba4ccf6c61157e4a563d1de4ed64d7afff92032bc9c5c9e2c125f9f245acf6683c40f3380b0a762c862859b3651a6a51aa1fdd3887e69eecf46cb60e2f6cf2fcf3d29341b2066dd56bb3f164448b6fa4cf4b1ae9312cb147a667350bdaffdd6c4d31
ERROR: Failed authentication of the data
$ TRIPLESEC_KEY=abc triplesec --compatibility dec 1c94d7de000000031355e46727ab2f1a1575a605e4aa5012dcf0e13e55891a4167b10a0f5c173a2e6c6cbb5718f3f7021005f2501b8b5b674bed2553687404aae7aed32d4e9a7bb456dbef209786ee14d974e7899a3d8bacfb7f6705f4abeb307047b1360fa2e5721e5e485361d3a59f426af89d6170fd67feba4ccf6c61157e4a563d1de4ed64d7afff92032bc9c5c9e2c125f9f245acf6683c40f3380b0a762c862859b3651a6a51aa1fdd3887e69eecf46cb60e2f6cf2fcf3d29341b2066dd56bb3f164448b6fa4cf4b1ae9312cb147a667350bdaffdd6c4d31
Hello world
6 changes: 6 additions & 0 deletions requirements.txt
@@ -0,0 +1,6 @@
pycryptodome==3.7.1
pysha3==1.0.2
salsa20==0.3.0
scrypt==0.8.6
six==1.11.0
twofish==0.3.0
18 changes: 9 additions & 9 deletions setup.py
Expand Up @@ -26,10 +26,10 @@

setup(
name = 'TripleSec',
version = '0.4',
version = '0.5',
description = 'a Python implementation of TripleSec',
author = 'Filippo Valsorda',
author_email = 'filippo.valsorda@gmail.com',
author = 'Keybase',
author_email = 'max@keybase.io',
url = 'http://github.com/keybase/python-triplesec',
packages = ['triplesec'],
license = 'BSD-new',
Expand All @@ -41,12 +41,12 @@
'Topic :: Security :: Cryptography',
'Topic :: Software Development :: Libraries'],
long_description = open('README.rst').read(),
install_requires = ["pycrypto",
"scrypt",
"six",
"pysha3",
"twofish",
"salsa20"],
install_requires = ["pycryptodome==3.7.1",
"scrypt==0.8.6",
"six==1.11.0",
"pysha3==1.0.2",
"twofish==0.3.0",
"salsa20==0.3.0"],
test_suite = 'nose.collector',
tests_require = tests_require,
**params
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
@@ -1,5 +1,5 @@
[tox]
envlist = py26,py27,py33
envlist = py27,py36

[testenv]
deps =
Expand Down
50 changes: 31 additions & 19 deletions triplesec/__init__.py
Expand Up @@ -26,16 +26,13 @@
_constant_time_compare,
win32_utf8_argv
)
from .versions import VERSIONS
from .versions import get_version, valid_version, LATEST_VERSION


### MAIN CLASS
class TripleSec():
LATEST_VERSION = 3
MAGIC_BYTES = MAGIC_BYTES

VERSIONS = VERSIONS

@staticmethod
def _check_key(key):
if key is None: return
Expand All @@ -56,10 +53,11 @@ def _check_output_type(data):
if not isinstance(data, six.binary_type):
raise TripleSecFailedAssertion(u"The return value was not binary")

def __init__(self, key=None):
def __init__(self, key=None, rndstream=None):
self._check_key(key)
self.key = key
self._extra_bytes = None
self.rndstream = rndstream

@staticmethod
def _key_stretching(key, salt, version, extra_bytes=0):
Expand Down Expand Up @@ -111,15 +109,15 @@ def encrypt_ascii(self, data, key=None, v=None, extra_bytes=0,
result = digestor(binary_result)
return result

def encrypt(self, data, key=None, v=None, extra_bytes=0):
def encrypt(self, data, key=None, v=None, extra_bytes=0, compatibility=False):
self._check_data(data)
self._check_key(key)
if key is None and self.key is None:
raise TripleSecError(u"You didn't initialize TripleSec with a key, so you need to specify one")
if key is None: key = self.key

if not v: v = self.LATEST_VERSION
version = self.VERSIONS[v]
if not v: v = LATEST_VERSION
version = get_version(v, compatibility)
result, extra = self._encrypt(data, key, version, extra_bytes)

self._check_output_type(result)
Expand All @@ -128,10 +126,12 @@ def encrypt(self, data, key=None, v=None, extra_bytes=0):
return result

def _encrypt(self, data, key, version, extra_bytes):
salt = rndfile.read(version.salt_size)
rndstream = self.rndstream or rndfile

salt = rndstream.read(version.salt_size)
mac_keys, cipher_keys, extra = self._key_stretching(key, salt, version, extra_bytes)

encrypted_material = self._encrypt_data(data, cipher_keys, version)
encrypted_material = self._encrypt_data(data, cipher_keys, version, rndstream)

header = b''.join(version.header)

Expand All @@ -153,11 +153,17 @@ def _generate_macs(authenticated_data, mac_keys, version):
return result

@staticmethod
def _encrypt_data(data, cipher_keys, version):
def _encrypt_data(data, cipher_keys, version, rndstream=None):
iv_datas = {}

# Generate the IVs in reverse order as per the reference JavaScript implementation
for n, c in reversed(list(enumerate(version.ciphers))):
iv_datas[n] = c.implementation.generate_iv_data(rndstream=rndstream)

# The keys order is from the outermost to the innermost
for n, c in enumerate(version.ciphers):
# the keys order is from the outermost to the innermost
key = cipher_keys[n]
data = c.implementation.encrypt(data, key)
data = c.implementation.encrypt(data, key, iv_datas[n])
return data

def decrypt_ascii(self, ascii_string, key=None, digest="hex"):
Expand All @@ -180,7 +186,7 @@ def decrypt_ascii(self, ascii_string, key=None, digest="hex"):
result = self.decrypt(binary_string, key)
return result

def decrypt(self, data, key=None):
def decrypt(self, data, key=None, compatibility=False):
self._check_data(data)
self._check_key(key)
if key is None and self.key is None:
Expand All @@ -191,10 +197,10 @@ def decrypt(self, data, key=None):
raise TripleSecError(u"This does not look like a TripleSec ciphertext")

header_version = struct.unpack(">I", data[4:8])[0]
if header_version not in self.VERSIONS:
if not valid_version(header_version):
raise TripleSecError(u"Unimplemented version: " + str(header_version))

version = self.VERSIONS[header_version]
version = get_version(header_version, compatibility)
result = self._decrypt(data, key, version)

self._check_output_type(result)
Expand Down Expand Up @@ -284,6 +290,9 @@ def main():
help="consider all input (key, plaintext, ciphertext) to be hex encoded; "
"hex encode all output")

parser.add_argument('--compatibility', action='store_true',
help="Use Keccak instead of SHA3 for the second MAC and reverse endianness of Salsa20 in version 1. Only effective in versions before 4.")

parser.add_argument('-k', '--key', help="the TripleSec key; "
"if not specified will check the TRIPLESEC_KEY env variable, "
"then prompt the user for it")
Expand Down Expand Up @@ -356,17 +365,20 @@ def main():
try:
if args._command == 'dec':
ciphertext = data if args.binary else binascii.unhexlify(data.strip())
plaintext = decrypt(ciphertext, key)
plaintext = decrypt(ciphertext, key, args.compatibility)
if args.binary:
getattr(sys.stdout, 'buffer', sys.stdout).write(plaintext)
elif args.hex:
print(binascii.hexlify(plaintext).decode())
else:
print(plaintext.decode('utf-8', 'replace'))
try:
print(plaintext.decode('ascii', 'strict'))
except UnicodeDecodeError:
sys.stderr.write("Aborting: unable to decode plaintext as ASCII. Use -b to output binary.\n")

elif args._command == 'enc':
plaintext = data
ciphertext = encrypt(plaintext, key)
ciphertext = encrypt(plaintext, key, args.compatibility)
stdout = getattr(sys.stdout, 'buffer', sys.stdout)
stdout.write(ciphertext if args.binary else binascii.hexlify(ciphertext) + b'\n')

Expand Down

0 comments on commit ee0c764

Please sign in to comment.