diff --git a/Dockerfile b/Dockerfile index e39919f..4abc0c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,17 @@ FROM python:alpine3.10 MAINTAINER Abderrahmane SMIMITE -RUN apk add build-base openssl-dev libffi-dev vim -RUN pip install cffi cryptography flask +RUN apk add build-base openssl-dev libffi-dev vim py-gunicorn RUN mkdir /app +COPY requirements.txt /app COPY app.py /app +COPY wsgi.py /app +COPY start.sh /app WORKDIR /app +RUN pip install -r requirements.txt + EXPOSE 5000 -CMD ["python3", "app.py"] \ No newline at end of file +CMD ["/app/start.sh"] \ No newline at end of file diff --git a/app.py b/app.py index 4268971..b00272d 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,5 @@ from flask import Flask,jsonify,request,abort +from flask_restplus import Resource, Api, fields, reqparse import json import base64 @@ -9,6 +10,16 @@ from cryptography.fernet import Fernet + +flask_app = Flask(__name__) +app = Api(app = flask_app, + version = "1.0", + title = "EOF (Encryption On the Fly)", + description = "Manage real-time encryption for your app with a simple API. Based on Fernet implementation which uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256. Proof Of Concept API with NO INFORMATION STORED.") + +name_space = app.namespace('v1', description='Encryption on the fly API') + + def genKey(password_provided): password = password_provided.encode() salt = b'rrdX9A66qXtQZwzf' # You NEED TO CHANGE THIS (eg. os.urandom(16)) must be of type bytes @@ -22,68 +33,81 @@ def genKey(password_provided): key = base64.urlsafe_b64encode(kdf.derive(password)) # Can only use kdf once return key -app = Flask(__name__) - ''' Secure Transport (TLS) is mandatory. This snippet is minimalist on purpose so to keep processing on the memory ''' -@app.route("/", methods=('GET',)) -def hello(): - return "App is Up!" +EncryptThis = app.model('Encrypt This', { + 'key': fields.String(required=True, description='Your encryption password that will be used to derivate the key'), + 'plaintext': fields.String(required=True, description='Data to encrypt') +}) + +DecryptThis = app.model('Decrypt This', { + 'key': fields.String(required=True, description='Your encryption password used to derivate the key'), + 'ciphertext': fields.String(required=True, description='Data to decrypt') +}) +@name_space.route('/health') +class Health(Resource): + def get(self): + '''Return system health status''' + return {'status': 'Everything looks fine'} -@app.route("/encrypt", methods=['POST']) -def encrypt(): + +@name_space.route('/encrypt') +class Encryption(Resource): ''' { "key":"secretKey", "plaintext":"Text containing sensitive data to be encrypted" } ''' - req_data = request.get_json() - - try: - key = req_data['key'] - plaintext = req_data['plaintext'] - except: - abort(400) - - message = plaintext.encode() - - try: - f = Fernet(genKey(key)) - encrypted = f.encrypt(message) - except: - abort(403) - - return json.dumps({'ciphertext':encrypted.decode()}) - - -@app.route("/decrypt", methods=['POST']) -def decrypt(): + @name_space.expect(EncryptThis) + def post(self): + '''Get a plaintext and the associated key and return ciphertext''' + try: + key = name_space.payload['key'] + plaintext = name_space.payload['plaintext'] + except: + abort(400) + + message = plaintext.encode() + + try: + f = Fernet(genKey(key)) + encrypted = f.encrypt(message) + except: + abort(403) + + return {'ciphertext':encrypted.decode()} + + +@name_space.route('/decrypt') +class Decryption(Resource): ''' { "key":"secretKey", "ciphertext":"gAAAAABdGQQgxejDKPqkR9tMGdHsL0ewJr3z3TOZeNC7-0AxBIxCv3gjAmng4ZrIY668ovifRMl1_F_5O64Wjbhn0qsm2Vn7UjbEhOEvXFlFIaK1ichVONWHr0sMGD5s30TNf7_9LEKN" } ''' - req_data = request.get_json() - try: - key = req_data['key'] - ciphertext = req_data['ciphertext'] - except: - abort(400) - - encrypted = ciphertext.encode() - - try: - f = Fernet(genKey(key)) - decrypted = f.decrypt(encrypted) - except: - abort(403) - - return json.dumps({'plaintext':decrypted.decode()}) - -if __name__ == "__main__": - app.run(host='0.0.0.0', port=5000) \ No newline at end of file + @name_space.expect(DecryptThis) + def post(self): + '''Get a ciphertext and the associated key and return plaintext''' + try: + key = name_space.payload['key'] + ciphertext = name_space.payload['ciphertext'] + except: + abort(400) + + encrypted = ciphertext.encode() + + try: + f = Fernet(genKey(key)) + decrypted = f.decrypt(encrypted) + except: + abort(403) + + return {'plaintext':decrypted.decode()} + +if __name__ == '__main__': + flask_app.run(debug=False) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a9faa85..ddeae1b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ -cffi==1.12.3 -cryptography==2.6.1 Flask==1.0.2 +flask_restplus==0.12.1 +cryptography==2.6.1 +cffi==1.12.3 +gunicorn==19.9.0 \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..82a8825 --- /dev/null +++ b/start.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env ash +cd /app +gunicorn --bind 0.0.0.0:5000 wsgi:flask_app \ No newline at end of file diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..d95ea26 --- /dev/null +++ b/wsgi.py @@ -0,0 +1,4 @@ +from app import flask_app + +if __name__ == "__main__": + flask_app.run() \ No newline at end of file