From e1437b1b03e32f184b2ad33992f2daaf563ef75a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= <philipp@hoerist.com> Date: Tue, 26 Jul 2016 19:47:05 +0200 Subject: [PATCH] Use native cryptography lib if installed --- omemo/omemo/aes_gcm.py | 168 ++++++-------------------------- omemo/omemo/aes_gcm_fallback.py | 152 +++++++++++++++++++++++++++++ omemo/omemo/aes_gcm_native.py | 61 ++++++++++++ omemo/omemo/state.py | 6 +- 4 files changed, 244 insertions(+), 143 deletions(-) create mode 100644 omemo/omemo/aes_gcm_fallback.py create mode 100644 omemo/omemo/aes_gcm_native.py diff --git a/omemo/omemo/aes_gcm.py b/omemo/omemo/aes_gcm.py index 0f47615b..5f067a0b 100644 --- a/omemo/omemo/aes_gcm.py +++ b/omemo/omemo/aes_gcm.py @@ -1,158 +1,46 @@ # -*- coding: utf-8 -*- # -# Copyright 2014 Jonathan Zdziarski <jonathan@zdziarski.com> +# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de> # -# All rights reserved. +# This file is part of python-omemo library. # -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: +# The python-omemo library is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. # -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. +# python-omemo is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. # -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. +# You should have received a copy of the GNU General Public License along with +# the python-omemo library. If not, see <http://www.gnu.org/licenses/>. # -# 3. Neither the name of the copyright holder nor the names of its contributors -# may be used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import logging -from struct import pack, unpack -from Crypto.Cipher import AES -from Crypto.Util import strxor +import sys +import logging log = logging.getLogger('gajim.plugin_system.omemo') +try: + from .aes_gcm_native import aes_decrypt + from .aes_gcm_native import aes_encrypt + log.debug('Using fast cryptography') +except ImportError: + from .aes_gcm_fallback import aes_decrypt + from .aes_gcm_fallback import aes_encrypt + log.debug('Using slow cryptography') -def gcm_rightshift(vec): - for x in range(15, 0, -1): - c = vec[x] >> 1 - c |= (vec[x - 1] << 7) & 0x80 - vec[x] = c - vec[0] >>= 1 - return vec - - -def gcm_gf_mult(a, b): - mask = [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01] - poly = [0x00, 0xe1] - - Z = [0] * 16 - V = [c for c in a] - - for x in range(128): - if b[x >> 3] & mask[x & 7]: - Z = [V[y] ^ Z[y] for y in range(16)] - bit = V[15] & 1 - V = gcm_rightshift(V) - V[0] ^= poly[bit] - return Z - - -def ghash(h, auth_data, data): - u = (16 - len(data)) % 16 - v = (16 - len(auth_data)) % 16 - - x = auth_data + chr(0) * v + data + chr(0) * u - x += pack('>QQ', len(auth_data) * 8, len(data) * 8) - - y = [0] * 16 - vec_h = [ord(c) for c in h] - - for i in range(0, len(x), 16): - block = [ord(c) for c in x[i:i + 16]] - y = [y[j] ^ block[j] for j in range(16)] - y = gcm_gf_mult(y, vec_h) - - return ''.join(chr(c) for c in y) - - -def inc32(block): - counter, = unpack('>L', block[12:]) - counter += 1 - return block[:12] + pack('>L', counter) - - -def gctr(k, icb, plaintext): - y = '' - if len(plaintext) == 0: - return y +def encrypt(key, iv, plaintext): + return aes_encrypt(key, iv, plaintext) - aes = AES.new(k) - cb = icb - for i in range(0, len(plaintext), aes.block_size): - cb = inc32(cb) - encrypted = aes.encrypt(cb) - plaintext_block = plaintext[i:i + aes.block_size] - y += strxor.strxor(plaintext_block, encrypted[:len(plaintext_block)]) - - return y - - -def gcm_decrypt(k, iv, encrypted, auth_data, tag): - aes = AES.new(k) - h = aes.encrypt(chr(0) * aes.block_size) - - if len(iv) == 12: - y0 = iv + "\x00\x00\x00\x01" +def decrypt(key, iv, ciphertext): + plaintext = aes_decrypt(key, iv, ciphertext).decode('utf-8') + if sys.version_info < (3, 0): + return unicode(plaintext) else: - y0 = ghash(h, '', iv) - - decrypted = gctr(k, y0, encrypted) - s = ghash(h, auth_data, encrypted) - - t = aes.encrypt(y0) - T = strxor.strxor(s, t) - if T != tag: - raise ValueError('Decrypted data is invalid') - else: - return decrypted - - -def gcm_encrypt(k, iv, plaintext, auth_data): - aes = AES.new(k) - h = aes.encrypt(chr(0) * aes.block_size) - - if len(iv) == 12: - y0 = iv + "\x00\x00\x00\x01" - else: - y0 = ghash(h, '', iv) - - encrypted = gctr(k, y0, plaintext) - s = ghash(h, auth_data, encrypted) - - t = aes.encrypt(y0) - T = strxor.strxor(s, t) - return (encrypted, T) - - -def aes_encrypt(key, nonce, plaintext): - """ Use AES128 GCM with the given key and iv to encrypt the payload. """ - c, t = gcm_encrypt(key, nonce, plaintext, '') - result = c + t - return result - - -def aes_decrypt(key, nonce, payload): - """ Use AES128 GCM with the given key and iv to decrypt the payload. """ - ciphertext = payload[:-16] - mac = payload[-16:] - return gcm_decrypt(key, nonce, ciphertext, '', mac) + return plaintext class NoValidSessions(Exception): diff --git a/omemo/omemo/aes_gcm_fallback.py b/omemo/omemo/aes_gcm_fallback.py new file mode 100644 index 00000000..a34255af --- /dev/null +++ b/omemo/omemo/aes_gcm_fallback.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2014 Jonathan Zdziarski <jonathan@zdziarski.com> +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from struct import pack, unpack + +from Crypto.Cipher import AES +from Crypto.Util import strxor + + +def gcm_rightshift(vec): + for x in range(15, 0, -1): + c = vec[x] >> 1 + c |= (vec[x - 1] << 7) & 0x80 + vec[x] = c + vec[0] >>= 1 + return vec + + +def gcm_gf_mult(a, b): + mask = [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01] + poly = [0x00, 0xe1] + + Z = [0] * 16 + V = [c for c in a] + + for x in range(128): + if b[x >> 3] & mask[x & 7]: + Z = [V[y] ^ Z[y] for y in range(16)] + bit = V[15] & 1 + V = gcm_rightshift(V) + V[0] ^= poly[bit] + return Z + + +def ghash(h, auth_data, data): + u = (16 - len(data)) % 16 + v = (16 - len(auth_data)) % 16 + + x = auth_data + chr(0) * v + data + chr(0) * u + x += pack('>QQ', len(auth_data) * 8, len(data) * 8) + + y = [0] * 16 + vec_h = [ord(c) for c in h] + + for i in range(0, len(x), 16): + block = [ord(c) for c in x[i:i + 16]] + y = [y[j] ^ block[j] for j in range(16)] + y = gcm_gf_mult(y, vec_h) + + return ''.join(chr(c) for c in y) + + +def inc32(block): + counter, = unpack('>L', block[12:]) + counter += 1 + return block[:12] + pack('>L', counter) + + +def gctr(k, icb, plaintext): + y = '' + if len(plaintext) == 0: + return y + + aes = AES.new(k) + cb = icb + + for i in range(0, len(plaintext), aes.block_size): + cb = inc32(cb) + encrypted = aes.encrypt(cb) + plaintext_block = plaintext[i:i + aes.block_size] + y += strxor.strxor(plaintext_block, encrypted[:len(plaintext_block)]) + + return y + + +def gcm_decrypt(k, iv, encrypted, auth_data, tag): + aes = AES.new(k) + h = aes.encrypt(chr(0) * aes.block_size) + + if len(iv) == 12: + y0 = iv + "\x00\x00\x00\x01" + else: + y0 = ghash(h, '', iv) + + decrypted = gctr(k, y0, encrypted) + s = ghash(h, auth_data, encrypted) + + t = aes.encrypt(y0) + T = strxor.strxor(s, t) + if T != tag: + raise ValueError('Decrypted data is invalid') + else: + return decrypted + + +def gcm_encrypt(k, iv, plaintext, auth_data): + aes = AES.new(k) + h = aes.encrypt(chr(0) * aes.block_size) + + if len(iv) == 12: + y0 = iv + "\x00\x00\x00\x01" + else: + y0 = ghash(h, '', iv) + + encrypted = gctr(k, y0, plaintext) + s = ghash(h, auth_data, encrypted) + + t = aes.encrypt(y0) + T = strxor.strxor(s, t) + return (encrypted, T) + + +def aes_encrypt(key, nonce, plaintext): + """ Use AES128 GCM with the given key and iv to encrypt the payload. """ + c, t = gcm_encrypt(key, nonce, plaintext, '') + result = c + t + return result + + +def aes_decrypt(key, nonce, payload): + """ Use AES128 GCM with the given key and iv to decrypt the payload. """ + ciphertext = payload[:-16] + mac = payload[-16:] + return gcm_decrypt(key, nonce, ciphertext, '', mac) diff --git a/omemo/omemo/aes_gcm_native.py b/omemo/omemo/aes_gcm_native.py new file mode 100644 index 00000000..40bf1271 --- /dev/null +++ b/omemo/omemo/aes_gcm_native.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de> +# +# This file is part of python-omemo library. +# +# The python-omemo library is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# python-omemo is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# the python-omemo library. If not, see <http://www.gnu.org/licenses/>. +# + + +import os +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers import algorithms +from cryptography.hazmat.primitives.ciphers.modes import GCM + +# On Windows we have to import a specific backend because the +# default_backend() mechanism doesnt work in Gajim for Windows. +# Its because of how Gajim is build with cx_freeze + +if os.name == 'nt': + from cryptography.hazmat.backends.openssl import backend +else: + from cryptography.hazmat.backends import default_backend + + +def aes_decrypt(key, iv, payload): + """ Use AES128 GCM with the given key and iv to decrypt the payload. """ + data = payload[:-16] + tag = payload[-16:] + if os.name == 'nt': + _backend = backend + else: + _backend = default_backend() + decryptor = Cipher( + algorithms.AES(key), + GCM(iv, tag=tag), + backend=_backend).decryptor() + return decryptor.update(data) + decryptor.finalize() + + +def aes_encrypt(key, iv, plaintext): + """ Use AES128 GCM with the given key and iv to encrypt the plaintext. """ + if os.name == 'nt': + _backend = backend + else: + _backend = default_backend() + encryptor = Cipher( + algorithms.AES(key), + GCM(iv), + backend=_backend).encryptor() + return encryptor.update(plaintext) + encryptor.finalize() + encryptor.tag diff --git a/omemo/omemo/state.py b/omemo/omemo/state.py index 92887abe..fb201886 100644 --- a/omemo/omemo/state.py +++ b/omemo/omemo/state.py @@ -35,7 +35,7 @@ from axolotl.state.prekeybundle import PreKeyBundle from axolotl.util.keyhelper import KeyHelper from Crypto.Random import get_random_bytes -from .aes_gcm import NoValidSessions, aes_decrypt, aes_encrypt +from .aes_gcm import NoValidSessions, decrypt, encrypt from .liteaxolotlstore import (LiteAxolotlStore, DEFAULT_PREKEY_AMOUNT, MIN_PREKEY_AMOUNT, SPK_CYCLE_TIME, SPK_ARCHIVE_TIME) @@ -210,7 +210,7 @@ class OmemoState: ' sid => ' + str(sid)) return - result = unicode(aes_decrypt(key, iv, payload)) + result = unicode(decrypt(key, iv, payload)) if self.own_jid == sender_jid: self.add_own_device(sid) @@ -263,7 +263,7 @@ class OmemoState: log.debug('Skipped own Device because Trust is: ' + str(self.isTrusted(cipher))) - payload = aes_encrypt(key, iv, plaintext) + payload = encrypt(key, iv, plaintext) result = {'sid': self.own_device_id, 'keys': encrypted_keys, -- GitLab