<repo_name>ejoftheweb/Minigmand<file_sep>/app/src/main/java/uk/co/platosys/minigma/utils/XMLUtils.java
package uk.co.platosys.minigma.utils;

import org.jdom2.Document;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.XMLOutputter;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import uk.co.platosys.minigma.Key;
import uk.co.platosys.minigma.Lock;
import uk.co.platosys.minigma.exceptions.BadPassphraseException;
import uk.co.platosys.minigma.exceptions.InvalidXMLException;

/**
 *  Static methods to handle org.jdom2.Documents
 */
public class XMLUtils {
    /**Takes Base64 encoded data and returns an org.jdom2.Document**/
    public static Document decode(String encoded) throws InvalidXMLException {

        SAXBuilder saxBuilder = new SAXBuilder();
        try{
            return saxBuilder.build(new ByteArrayInputStream(MinigmaUtils.decode(encoded)));
        }catch(Exception x){
            throw new InvalidXMLException("invalid xml", x);
        }

    }
    /**Encodes an org.jdom2.Document object as Base64 text**/
    public static String encode(Document document) throws IOException {
        XMLOutputter xmlOutputter = new XMLOutputter();
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            xmlOutputter.output(document, byteArrayOutputStream);
            return MinigmaUtils.encode(byteArrayOutputStream.toByteArray(),true);
        }catch(IOException iox){
            throw iox;
        }catch(Exception x){

        }
        return null;
    }
    /**Takes encrypted data in the form of Base64 text, the Key for which it was encrypted and its passphrase and returns an org.jdom2.Document object*/
    public static Document decrypt(String encrypted, Key key, char[] passphrase) throws InvalidXMLException, BadPassphraseException {
        //TODO
        return null;

    }
    /**Encrypts an org.jdom2.Document object to the PGP Public Key represented by the supplied Lock object*/
    public static String encrypt(Document document, Lock lock){
        //TODO
        return null;
    }

}
<file_sep>/app/src/test/java/uk/co/platosys/minigma/VerifySignatureTest.java
package uk.co.platosys.minigma;

import org.junit.Before;
import org.junit.Test;
import uk.co.platosys.minigma.exceptions.Exceptions;
import uk.co.platosys.minigma.exceptions.MinigmaException;
import uk.co.platosys.minigma.utils.Kidney;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertTrue;

public class VerifySignatureTest {

    LockStore lockstore;
    //String username = TestValues.testUsernames[0];
    Map<Fingerprint, String> createdFingerprints=new HashMap<>();
    @Before
    public  void setup(){
        try {
            if (lockstore==null){lockstore=new MinigmaLockStore(TestValues.lockFile, true);}
            File keysDirectory = TestValues.keyDirectory;
            if (!keysDirectory.exists()) {
                keysDirectory.mkdirs();
                for (int i = 0; i < TestValues.testPassPhrases.length; i++) {
                    Lock lock = LockSmith.createLockset(TestValues.keyDirectory, lockstore,  TestValues.testPassPhrases[i].toCharArray(), Algorithms.RSA);
                    createdFingerprints.put(lock.getFingerprint(), TestValues.testPassPhrases[i]);
                }
            }

        }catch(Exception x){
            Exceptions.dump("CTSCSetup", x);
        }
    }
    @Test
    public void verifySignatureTest(){
        Key key=null;
        Lock lock=null;
        File signatureFile=null;
        LockStore lockStore=null;
        for(Fingerprint fingerprint:createdFingerprints.keySet()){
         try {
            key = new Key(new File(TestValues.keyDirectory, fingerprint.toBase64String()));
            Signature signature = key.sign(TestValues.testText, TestValues.testPassPhrases[0].toCharArray());
            System.out.println(Kidney.toString(signature.getKeyID())+":"+signature.getShortDigest());
            signatureFile = new File(TestValues.signatureDirectory, signature.getShortDigest());
            if (signatureFile.exists()) {
                signatureFile.delete();
            }
            signature.encodeToFile(signatureFile);
            lock = lockStore.getLock(fingerprint);
            //System.out.println(Kidney.toString(lock.getLockID()));
        }catch(Exception e) {
            System.out.println("VST2 "+e.getClass().getName()+"\n "+ e.getMessage());
            StackTraceElement[] stackTraceElements = e.getStackTrace();
            for (StackTraceElement stackTraceElement:stackTraceElements){
                System.out.println(stackTraceElement.toString());
            }
        }try{
            Signature rereadSignature = new Signature(signatureFile);
            //System.out.println(Kidney.toString(rereadSignature.getKeyID()));

            assertTrue(lock.verify(TestValues.testText,rereadSignature));
        }catch (Exception e){
            System.out.println("VST3 "+ e.getMessage());
            StackTraceElement[] stackTraceElements = e.getStackTrace();
            for (StackTraceElement stackTraceElement:stackTraceElements){
                System.out.println(stackTraceElement.toString());
            }
        }}
    }
    @Test
    public void verifySignatureNotationsTest(){
        Key key=null;
        Lock lock=null;
        File signatureFile=null;
        LockStore lockStore=null;
        try {
            lockStore = new MinigmaLockStore(new File(TestValues.lockDirectory, "lockstore"), false);
        }catch (MinigmaException e){
            Exceptions.dump(e);
        }
        for(Fingerprint fingerprint:createdFingerprints.keySet()){
        try {
            key = new Key(new File(TestValues.keyDirectory,fingerprint.toBase64String()));
            List<Notation> notationList = new ArrayList<>();
            for (int i=0; i<TestValues.testNotationNames.length; i++){
                Notation notation = new Notation(TestValues.testNotationNames[i], TestValues.testNotationValues[i]);
                notationList.add(notation);
            }
            Signature signature = key.sign(TestValues.testText, notationList, TestValues.testPassPhrases[0].toCharArray());
            System.out.println(Kidney.toString(signature.getKeyID())+":"+signature.getShortDigest());
            signatureFile = new File(TestValues.signatureDirectory, signature.getShortDigest());
            if (signatureFile.exists()) {
                signatureFile.delete();
            }
            signature.encodeToFile(signatureFile);
            lock = lockStore.getLock(fingerprint);
            //System.out.println(Kidney.toString(lock.getLockID()));
        }catch(Exception e) {
            Exceptions.dump(e);
        }try{
            Signature rereadSignature = new Signature(signatureFile);
            List<Notation> notations = rereadSignature.getNotations();
            for(Notation notation:notations){
                String notationName = notation.getName();
                String notationValue = notation.getValue();
                System.out.println(notationName + ":"+notationValue);
            }

            assertTrue(lock.verify(TestValues.testText,rereadSignature));
        }catch (Exception e){
           Exceptions.dump(e);
        }
    }}
}
<file_sep>/app/src/main/java/uk/co/platosys/minigma/votes/InvalidBallotException.java
package uk.co.platosys.minigma.votes;

public class InvalidBallotException extends Exception {
    public InvalidBallotException(String msg) {
        super(msg);
    }
}<file_sep>/app/src/main/java/uk/co/platosys/minigma/Lock.java
/*
 * Copyright <NAME> and Platosys.
/* (c) copyright 2018 Platosys
        * MIT Licence
        * Permission is hereby granted, free of charge, to any person obtaining a copy
        * of this software and associated documentation files (the "Software"), to deal
        * in the Software without restriction, including without limitation the rights
        * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        * copies of the Software, and to permit persons to whom the Software is
        * furnished to do so, subject to the following conditions:
        *
        *The above copyright notice and this permission notice shall be included in all
        * copies or substantial portions of the Software.
        *
        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        * SOFTWARE.*/
package uk.co.platosys.minigma;



import android.util.Log;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.spongycastle.bcpg.ArmoredInputStream;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.bcpg.attr.ImageAttribute;
import org.spongycastle.openpgp.*;
import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.util.Arrays;

import uk.co.platosys.minigma.exceptions.BadPassphraseException;
import uk.co.platosys.minigma.exceptions.Exceptions;
import uk.co.platosys.minigma.exceptions.MinigmaException;
import uk.co.platosys.minigma.exceptions.MinigmaOtherException;
import uk.co.platosys.minigma.exceptions.SignatureException;
import uk.co.platosys.minigma.exceptions.UnsupportedAlgorithmException;
import uk.co.platosys.minigma.utils.Kidney;
import uk.co.platosys.minigma.utils.MinigmaOutputStream;
import uk.co.platosys.minigma.utils.MinigmaUtils;


/**
 *  In Minigma, a Lock is the object used to lock something; once locked, it can
 * only be opened with a matching Key.
 *
 * Minigma Keys and Locks correspond to private keys and
 * public keys in other asymmetric crypto systems.
 *
 * Minigma is a fairly lightweight wrapper to OpenPGP, so a Minigma Lock can be instantiated
 * from OpenPGP public key material.
 *
 * Locks can be concatenated, so one can be instantiated for a group of people. If 
 * this concatenated Lock is used to lock something, the locked object can be unlocked
 * by ANY of the corresponding Keys. We have plans for, but have not yet implemented a Lock concatenation
 * in which ALL of the corresponding Keys are required.
 *
 * A Lock object is normally instantiated by obtaining it from a LockStore.
 *
 * LockIDs are 64-bit longs  but are not guaranteed to be unique. Neither, for that matter, are Fingerprints,
 * which are 160-bit numbers, but probabilistically they are. Better to use Fingerprints to identify Locks rather than
 * LockIDs, but sometimes the LockID is cool.
 *
 * @author edward
 *
 * Many Minigma methods, including several in the Lock class, are overloaded so that they can take either
 * a String, a BigBinary or a byte[] argument.
 */
public class Lock {

    private PGPPublicKeyRingCollection publicKeyRingCollection;
    private static String TAG = "Lock";
    private KeyFingerPrintCalculator calculator;
    //public static final String MULTIPLE_LOCK="multiple lock";
    private long lockID;
    private Fingerprint fingerprint;
    private PGPPublicKey publicKey;
    private List<String> userIDs = new ArrayList<>();
    private String shortID;



    /**
     * Creates a Lock object from base64-encoded OpenPGP public key material
     * @param encoded the base64-encoded string containing the public key
     */
    public Lock(String encoded)throws MinigmaException{
        byte[] bytes = MinigmaUtils.decode(encoded);

        init(bytes);
    }

    /**
     * byte array constructor
     * @param lockbytes
     * @throws MinigmaException
     */
    public Lock(byte[] lockbytes) throws MinigmaException{
        init(lockbytes);
    }
    private void init(byte[] bytes) throws MinigmaException{
        try{
            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
            KeyFingerPrintCalculator keyFingerPrintCalculator = new JcaKeyFingerprintCalculator();
            this.publicKeyRingCollection = new PGPPublicKeyRingCollection(bis, keyFingerPrintCalculator);
            PGPPublicKeyRing keyRing = (PGPPublicKeyRing) publicKeyRingCollection.getKeyRings().next();
            this.publicKey = keyRing.getPublicKey();
            Iterator<String> userids = publicKey.getUserIDs();
            while (userids.hasNext()){
                userIDs.add(userids.next());
            }

            this.lockID = publicKey.getKeyID();
            this.fingerprint = new Fingerprint(publicKey.getFingerprint());
            this.shortID = MinigmaUtils.encode(publicKey.getKeyID());

        }catch(Exception x){
            throw new MinigmaException("error initialising minigma-lock from string", x);
        }
    }
    /**
     * Instantiates a Lock from an AsciiArmored file
     *
     */
    public Lock(File file) throws MinigmaException{
        try {
            ArmoredInputStream armoredInputStream = new ArmoredInputStream(new FileInputStream(file));
            KeyFingerPrintCalculator calculator = new JcaKeyFingerprintCalculator();
            PGPPublicKeyRingCollection keyRings=new PGPPublicKeyRingCollection(armoredInputStream, calculator);
            init(keyRings);
        }catch(IOException iox){
            throw new MinigmaException("Lock(file) error opening lock file", iox);
        }catch(PGPException pex){
            throw new MinigmaException("Lock(file) error instantiating KeyRingCollection", pex);
        }
    }
    /**
     * This constructor takes a BouncyCastle PGPPublicKeyRingCollection and
     * instantiates a Lock from the first public key ring in the collection.
     * @param publicKeyRingCollection
     */

    protected Lock(PGPPublicKeyRingCollection publicKeyRingCollection) {
        init(publicKeyRingCollection);
    }
    private void init(PGPPublicKeyRingCollection publicKeyRingCollection){
        try {
            this.publicKeyRingCollection = publicKeyRingCollection;
            if(publicKeyRingCollection.getKeyRings().hasNext()) {
                PGPPublicKeyRing keyRing = (PGPPublicKeyRing) publicKeyRingCollection.getKeyRings().next();
                PGPPublicKey pubkey = keyRing.getPublicKey();
                this.publicKey = pubkey;
                Iterator<String> userids = pubkey.getUserIDs();
                while (userids.hasNext()){
                    userIDs.add(userids.next());
                }

                this.lockID = publicKey.getKeyID();
                this.fingerprint = new Fingerprint(publicKey.getFingerprint());
                this.shortID = MinigmaUtils.encode(publicKey.getKeyID());
            }else{
                System.out.println("no lock exists");
            }
            //System.out.println(Kidney.toString(lockID));
            //System.out.println(Kidney.toString(fingerprint));
        }catch(Exception x){
            Exceptions.dump("Lock-init", x);

        }
    }
    /**
     *
     */
    protected Lock(PGPPublicKeyRing pgpPublicKeyRing){
        try {
            Collection<PGPPublicKeyRing> keyList= new ArrayList<PGPPublicKeyRing>();
            keyList.add(pgpPublicKeyRing);
            this.publicKeyRingCollection = new PGPPublicKeyRingCollection(keyList);
        }catch (Exception x){
            Exceptions.dump("Lock+PKR", x);
        }
        PGPPublicKey pubkey = pgpPublicKeyRing.getPublicKey();
        this.publicKey=pubkey;
        Iterator<String> userids = pubkey.getUserIDs();
        while (userids.hasNext()){
            userIDs.add(userids.next());
        }

        this.lockID = publicKey.getKeyID();
        this.fingerprint = new Fingerprint(publicKey.getFingerprint());
        this.shortID = MinigmaUtils.encode(publicKey.getKeyID());

    }

    /**
     Encrypts a String with this Lock
     */
    public  byte[] lock(String string) throws MinigmaException{
        return lock(MinigmaUtils.toByteArray(string));

    }

    /**
     Encrypts a byte array with this Lock
     */
    public  byte[] lock(byte[] literalData) throws MinigmaException{
        //MinigmaUtils.printBytes(literalData);
        byte[] compressedData = MinigmaUtils.compress(literalData);
        //MinigmaUtils.printBytes(compressedData);
        byte[] encryptedData=CryptoEngine.encrypt(compressedData, this);
        return encryptedData;
    }
    /**
     * Encrypts a BigBinary with this Lock
     *
     */
    public BigBinary lock(BigBinary clearBytes) throws MinigmaException{
        return new BigBinary(lock(clearBytes.toByteArray()));
    }

    /**Encrypts the given String and returns the cyphertext as a String.
     *
     * @param string
     * @return
     * @throws MinigmaException
     */
    public String lockAsString(String string) throws MinigmaException{
        return MinigmaUtils.encode(lock(string));
    }

    /**
     * Returns this Lock as an ASCII-Armored String, e.g. for submitting to
     * keyservers.
     * @return
     */
    public String toArmoredString(){
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            MinigmaOutputStream minigmaOutputStream = new MinigmaOutputStream(byteArrayOutputStream);
            minigmaOutputStream.write(getBytes());
            minigmaOutputStream.flush();
            minigmaOutputStream.close();
            byte[] bytes = byteArrayOutputStream.toByteArray();
            String string = MinigmaUtils.fromByteArray(bytes);
            Log.d(TAG, string);
            return string;

        }catch(Exception x){
            Exceptions.dump(x);
            return null;
        }

    }
  /**
     * @return true if it verifies against this Lock, false otherwise.
     * @throws MinigmaException
     * @throws UnsupportedAlgorithmException
     * @throws SignatureException if the signature does not verify correctly.
     */
    public  boolean verify(String signedMaterial, Signature signature)throws MinigmaException, UnsupportedAlgorithmException, SignatureException {
        List<List<Fingerprint>> results= SignatureEngine.verify(signedMaterial, signature, this);
        List<Fingerprint> signorIDS=results.get(0);
        if(signorIDS.contains(fingerprint)){

            return true;
        }else{
            return false;
        }
    }


    /**
     * Adds a Lock to this lock, concatenating the two. Material locked with the
     * resulting concatenated Lock can be unlocked with *any* of the corresponding
     * Keys, unless inclusive is true in which case *all* the Keys are needed. However,
     * this feature is not yet implemented and passing inclusive as true will cause an exception to be thrown.
     * @param lock the Lock to be added to this Lock
     * @param inclusive must be false in this implementation.
     * @return a Lock which can be unlocked by the keys corresponding to either Lock.
     */
    public Lock addLock(Lock lock, boolean inclusive) throws MinigmaException{
        if(inclusive){throw new MinigmaException("inclusive Lock concatenation not yet implemented");}
        long newLockID=0;
        try{
            Iterator<PGPPublicKeyRing> pgpPublicKeyRingIterator = lock.getPGPPublicKeyRingIterator();
            while(pgpPublicKeyRingIterator.hasNext()){
                PGPPublicKeyRing pgpPublicKeyRing = pgpPublicKeyRingIterator.next();
                newLockID = pgpPublicKeyRing.getPublicKey().getKeyID();
                if (!(publicKeyRingCollection.contains(newLockID))){
                    publicKeyRingCollection = PGPPublicKeyRingCollection.addPublicKeyRing(publicKeyRingCollection, pgpPublicKeyRing);
                    //System.out.println("Lock: added lock with ID:"+Kidney.toString(newLockID)+" to lock "+Kidney.toString(lockID));
                }
            }
            //System.out.println("concatenation completed, this lock now has "+publicKeys.size()+" locks");
        }catch(Exception x){
            throw new MinigmaException("Error concatenating Lock", x);
        }
        return this;
    }

    /**
     * Removes a lock. Use this method with caution! it removes all references to any public key referred to by the Lock argument.
     * This could include a key that has been added by way of another Lock. So remove carefully.
     * @param lock the Lock to be removed;
     * @return this Lock, but with the other Lock removed
     */
    public Lock removeLock(Lock lock)throws MinigmaException{
        Iterator<PGPPublicKeyRing> pgpPublicKeyRingIterator = lock.getPGPPublicKeyRingIterator();
        try{
            while(pgpPublicKeyRingIterator.hasNext()){
                PGPPublicKeyRing pgpPublicKeyRing = pgpPublicKeyRingIterator.next();
                long keyID = pgpPublicKeyRing.getPublicKey().getKeyID();
                if (publicKeyRingCollection.contains(keyID)){
                    publicKeyRingCollection=PGPPublicKeyRingCollection.removePublicKeyRing(publicKeyRingCollection, pgpPublicKeyRing);
                }
            }
        }catch(Exception x){
            throw new MinigmaException("Error de-concatenating Lock", x);
        }
        return this;
    }

    /**Adds a textual ID - such as an email address - to the Lock corresponding to the passed-in key
     * @param id - the String id
     * @param key the Key to certify the new Lock
     * @param passphrase the passphrase to the Key
     *
     * */
    public Certificate addID (String id, Key key, char[] passphrase)throws MinigmaOtherException, BadPassphraseException{

        long lockid = key.getKeyID();
        PGPPublicKey pgpPublicKey;
        PGPSignatureGenerator pgpSignatureGenerator;
        PGPPrivateKey pgpPrivateKey;
        PGPSignature certificationSignature;
        try {
           pgpPublicKey = publicKeyRingCollection.getPublicKey(lockid);
        }catch(PGPException px){
            throw new MinigmaOtherException("lock not found corresponding to key with id:"+Kidney.toString(lockid));
        }try {
            pgpSignatureGenerator = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpPublicKey.getAlgorithm(), HashAlgorithmTags.SHA512));
            PBESecretKeyDecryptor pbeSecretKeyDecryptor = new JcePBESecretKeyDecryptorBuilder().build(passphrase);
            pgpPrivateKey = key.getSigningKey().extractPrivateKey(pbeSecretKeyDecryptor);
        }catch(PGPException px) {
            throw new BadPassphraseException("Bad passphrase supplied for Key with id "+lockid);
        }try{
            pgpSignatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, pgpPrivateKey);
            certificationSignature = pgpSignatureGenerator.generateCertification(id, pgpPublicKey);
            publicKey = PGPPublicKey.addCertification(pgpPublicKey, id, certificationSignature);
        }catch(PGPException px){
            throw new MinigmaOtherException("failed to certify new ID");
        }try {
            return new Certificate(certificationSignature);
        }catch(MinigmaException mx){
            throw new MinigmaOtherException("failed to create Minigma Certificate from PGP certification signature", mx);
        }
    }


    /**Adds a photo or image id to the Lock corresponding to the passed-in key. At the moment, only
     * jpeg photos are supported, a limitation that comes from the underlying BouncyCastle implementation but
     * apparently only because it's the only type for which a constant has been declared in ImageAttribute.
     * @param photodata - the String id
     * @param key the Key to certify the new Lock
     * @param passphrase the passphrase to the Key
     *
     * */
    public Certificate addImageID (byte[] photodata, Key key, char[] passphrase)throws MinigmaOtherException, BadPassphraseException{

        long lockid = key.getKeyID();
        PGPPublicKey pgpPublicKey;
        PGPSignatureGenerator pgpSignatureGenerator;
        PGPPrivateKey pgpPrivateKey;
        PGPSignature certificationSignature;
        try {
            pgpPublicKey = publicKeyRingCollection.getPublicKey(lockid);
        }catch(PGPException px){
            throw new MinigmaOtherException("lock not found corresponding to key with id:"+Kidney.toString(lockid));
        }try {
            pgpSignatureGenerator = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpPublicKey.getAlgorithm(), HashAlgorithmTags.SHA512));
            PBESecretKeyDecryptor pbeSecretKeyDecryptor = new JcePBESecretKeyDecryptorBuilder().build(passphrase);
            pgpPrivateKey = key.getSigningKey().extractPrivateKey(pbeSecretKeyDecryptor);
        }catch(PGPException px) {
            throw new BadPassphraseException("Bad passphrase supplied for Key with id "+lockid);
        }try{
            PGPUserAttributeSubpacketVectorGenerator pgpUserAttributeSubpacketVectorGenerator = new PGPUserAttributeSubpacketVectorGenerator();
            pgpUserAttributeSubpacketVectorGenerator.setImageAttribute(ImageAttribute.JPEG, photodata);
            PGPUserAttributeSubpacketVector pgpUserAttributeSubpacketVector = pgpUserAttributeSubpacketVectorGenerator.generate();
            pgpSignatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, pgpPrivateKey);
            certificationSignature = pgpSignatureGenerator.generateCertification(pgpUserAttributeSubpacketVector, pgpPublicKey);
            publicKey = PGPPublicKey.addCertification(pgpPublicKey,certificationSignature);
        }catch(PGPException px){
            throw new MinigmaOtherException("failed to certify new ID");
        }try {
            return new Certificate(certificationSignature);
        }catch(MinigmaException mx){
            throw new MinigmaOtherException("failed to create Minigma Certificate from PGP certification signature", mx);
        }
    }

    /**Revokes a particular public key in a Lock, generating a key revocation Certificate
     *
     * @param keyID the 64-bit ID of the public key to be revoked
     * @param key its corresponding key
     * @param passphrase and the passphrase
     * @return
     */
    public Certificate revokeLock (long keyID, Key key, char[] passphrase) throws MinigmaException{
        try {
            PGPPublicKeyRing pgpPublicKeyRing = publicKeyRingCollection.getPublicKeyRing(keyID);
            PGPPublicKey pgpPublicKey = pgpPublicKeyRing.getPublicKey(keyID);
            return revokeLock(pgpPublicKey, key, passphrase);
        }catch(PGPException pgpx){
            Exceptions.dump(pgpx);
            return null;
        }

    }
    /**Revokes a particular public key in a Lock, generating a key revocation Certificate
     *
     * @param fingerprint The fingerprint of the public key to be revoked
     * @param key
     * @param passphrase
     * @return
     */
    public Certificate revokeLock (Fingerprint fingerprint, Key key, char[] passphrase) throws MinigmaException{
        byte[] keyID = fingerprint.getFingerprintbytes();
        try {
            PGPPublicKeyRing pgpPublicKeyRing = publicKeyRingCollection.getPublicKeyRing(keyID);
            PGPPublicKey pgpPublicKey = pgpPublicKeyRing.getPublicKey(keyID);
            return revokeLock(pgpPublicKey, key, passphrase);
        }catch(PGPException pgpx){
            Exceptions.dump(pgpx);
            return null;
        }
    }
    private Certificate revokeLock (PGPPublicKey pgpPublicKey, Key key, char[] passphrase) throws MinigmaException{
        try{
            PGPSignatureGenerator pgpSignatureGenerator = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpPublicKey.getAlgorithm(), HashAlgorithmTags.SHA512));
            PBESecretKeyDecryptor pbeSecretKeyDecryptor = new JcePBESecretKeyDecryptorBuilder().build(passphrase);
            PGPPrivateKey pgpPrivateKey = key.getSigningKey().extractPrivateKey(pbeSecretKeyDecryptor);
            pgpSignatureGenerator.init(0x20, pgpPrivateKey);
            PGPSignature revocationSignature = pgpSignatureGenerator.generateCertification(pgpPublicKey);
            publicKey = PGPPublicKey.addCertification(pgpPublicKey,revocationSignature);
            return new Certificate(revocationSignature);
        }catch(PGPException pgpex){
            Exceptions.dump(pgpex);
            return null;
        }
    }
    public Certificate addDesignatedRevoker (byte[] lockid, Key key, char[] passphrase){
        try {
            PGPSignatureSubpacketGenerator pgpSignatureSubpacketGenerator = new PGPSignatureSubpacketGenerator();
            pgpSignatureSubpacketGenerator.setRevocationKey(true, PublicKeyAlgorithmTags.RSA_SIGN, lockid);
            PGPSignatureSubpacketVector pgpSignatureSubpacketVector = pgpSignatureSubpacketGenerator.generate();
            PGPSignatureGenerator pgpSignatureGenerator = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(PublicKeyAlgorithmTags.RSA_SIGN, HashAlgorithmTags.SHA512));
            PBESecretKeyDecryptor pbeSecretKeyDecryptor = new JcePBESecretKeyDecryptorBuilder().build(passphrase);
            PGPPrivateKey pgpPrivateKey = key.getMasterKey().extractPrivateKey(pbeSecretKeyDecryptor);
            pgpSignatureGenerator.init(PGPSignature.DIRECT_KEY,pgpPrivateKey);
            pgpSignatureGenerator.setHashedSubpackets(pgpSignatureSubpacketVector);
            PGPSignature revokerSignature = pgpSignatureGenerator.generate();
            publicKey = PGPPublicKey.addCertification(publicKey,revokerSignature);
            return new Certificate(revokerSignature);

        }catch(Exception x){
            Exceptions.dump(x);
            return null;
        }
    }

    /**
     *
     * @return
     */
    public Iterator<PGPPublicKeyRing> getPGPPublicKeyRingIterator(){

        return publicKeyRingCollection.getKeyRings();
    }

    /**
     *
     * @return
     */
    protected PGPPublicKeyRingCollection getKeyRings(){
        try{
            return publicKeyRingCollection;
        }catch(Exception ex){
            return null;
        }
    }

    /**
     *
     * @param keyID
     * @return
     */
    protected PGPPublicKeyRing getPublicKeyRing(long keyID){
        try{
            return publicKeyRingCollection.getPublicKeyRing(keyID);
        }catch(Exception e){
            return null;
        }
    }
    /**
     *
     * @param keyID
     * @return
     */
    public PGPPublicKey getPublicKey(long keyID){
        PGPPublicKeyRing pkr = getPublicKeyRing(keyID);
        return pkr.getPublicKey();
    }
/*
public void revoke(long keyID, Key key, char[] passphrase) throws MinigmaException {
    if(publicKeys.containsKey(keyID)){
        PGPPublicKeyRing pkr = publicKeys.get(keyID);
        PGPPublicKey publicKey = pkr.getPublicKey(keyID);
        PGPPublicKey.addCertification(publicKey, null)
    }else{
        throw new MinigmaException ("key "+Kidney.toString(keyID)+ " not in this lock");
    }
}
 */
    /**
     *Certifies a specific PGP public subkey within this Lock.
     * (e.g. master, encryption, subkey)
     * @param keyID the keyID of the public key to be certified
     * @param key the key of the person doing the certifying
     * @param passphrase the corresponding passphrase
     * @param certificationLevel the level of certification - basic to positive?
     * @throws MinigmaException
     */
    public Certificate certify(long keyID, Key key, char [] passphrase, LockStore lockStore, int certificationLevel) throws MinigmaException {
        if (!(Arrays.contains(Certificate.CERTIFICATION_TYPES, certificationLevel))){
            throw new MinigmaException(certificationLevel+ "is not a valid certification type");
        }
        try{
            if(publicKeyRingCollection.contains(keyID)){
                try{
                    PGPPublicKeyRing pgpPublicKeyRing = publicKeyRingCollection.getPublicKeyRing(keyID);
                    PGPPublicKey pgpPublicKey = pgpPublicKeyRing.getPublicKey(keyID);
                    boolean isCertified = false;
                    Iterator signatures = pgpPublicKey.getSignatures();
                    //first check to see if it is already certified by this key, no point in duplicating the effort
                    while(signatures.hasNext()){
                        PGPSignature signature = (PGPSignature) signatures.next();
                        if(signature.isCertification()){
                            isCertified=(signature.getKeyID()==key.getKeyID());
                            if(isCertified){return new Certificate(signature);}
                        }
                    }
                    if(! isCertified) {
                        Certificate certificate = SignatureEngine.getKeyCertification(key, passphrase, publicKey, certificationLevel);
                        //Now to add the certification to the PGPpublic key itself.
                        PGPSignature pgpSignature = certificate.getPgpSignature();
                        publicKeyRingCollection = PGPPublicKeyRingCollection.removePublicKeyRing(publicKeyRingCollection, pgpPublicKeyRing);
                        pgpPublicKeyRing = PGPPublicKeyRing.removePublicKey(pgpPublicKeyRing, publicKey);
                        publicKey = PGPPublicKey.addCertification(publicKey, pgpSignature);
                        pgpPublicKeyRing = PGPPublicKeyRing.insertPublicKey(pgpPublicKeyRing, publicKey);
                        publicKeyRingCollection = PGPPublicKeyRingCollection.addPublicKeyRing(publicKeyRingCollection, pgpPublicKeyRing);
                        lockStore.addLock(this);
                        return certificate;
                    }
                }catch(Exception x){
                    throw new MinigmaException("Problem certifying key", x);
                }
            }else{
                throw new MinigmaException ("key "+Kidney.toString(keyID)+ " not in this lock");
            }
        }catch(Exception x){
            Exceptions.dump(x);
            throw new MinigmaException("certification issues", x);

        }
        return null;
    }
    /*public void addCertificate(Certificate certificate){
        PGPSignature pgpSignature = certificate.getPgpSignature();

        publicKeyRingCollection = PGPPublicKeyRingCollection.removePublicKeyRing(publicKeyRingCollection, pgpPublicKeyRing);
        pgpPublicKeyRing = PGPPublicKeyRing.removePublicKey(pgpPublicKeyRing, publicKey);
        publicKey = PGPPublicKey.addCertification(publicKey, pgpSignature);
        pgpPublicKeyRing = PGPPublicKeyRing.insertPublicKey(pgpPublicKeyRing, publicKey);
        publicKeyRingCollection = PGPPublicKeyRingCollection.addPublicKeyRing(publicKeyRingCollection, pgpPublicKeyRing);
        lockStore.addLock(this);
        return certificate;
    }*/

    public List<Certificate> getCertificates() throws MinigmaException{
        List<Certificate> certificates = new ArrayList<>();
        for (PGPPublicKeyRing pgpPublicKeyRing : publicKeyRingCollection){
            Iterator<PGPPublicKey> pgpPublicKeyIterator = pgpPublicKeyRing.getPublicKeys();
            while (pgpPublicKeyIterator.hasNext()){
                PGPPublicKey pgpPublicKey = pgpPublicKeyIterator.next();
                Iterator signatureIterator = pgpPublicKey.getSignatures();
                while (signatureIterator.hasNext()) {
                    try {
                        PGPSignature pgpSignature = (PGPSignature) signatureIterator.next();
                        if (pgpSignature.isCertification()) {
                            long keyID = pgpSignature.getKeyID();
                           // String signerUserID = lockStore.getUserID(keyID);
                            Certificate certificate = new Certificate(pgpSignature);
                            certificates.add(certificate);
                        }

                    }catch (ClassCastException ccx){
                        Exceptions.dump(ccx);
                        //TODO handle
                    }
                }
            }
        }
        return certificates;
    }

    public boolean contains (long lockID) {
        try {
            return publicKeyRingCollection.contains(lockID);
        }catch (Exception x){
            Exceptions.dump(x);
            return false;
        }
    }
    public boolean contains (byte[] lockID) {
        try {
            return publicKeyRingCollection.contains(lockID);
        }catch (Exception x){
            Exceptions.dump(x);
            return false;
        }
    }


    /**
     *
     * @return
     */
    public long getLockID(){
        return lockID;
    }
    public Fingerprint getFingerprint(){
        return fingerprint;
    }
    public byte[] getBytes(){
        try {
            return publicKeyRingCollection.getEncoded();
        }catch(IOException iox){
            Exceptions.dump(iox);
            return null;
        }
    }

    /**Lock syncing syncs two versions of the same Lock (Locks accrue certifications). When a Lockstore
     * encounters two versions of the same Lock (that is, having the same fingerprint)it merges them.
     *
     * @param otherLock
     * @throws MinigmaException
     */
    /*public void syncLocks(Lock otherLock)throws MinigmaException {
        if(!(otherLock.getFingerprint().equals(fingerprint))){
            throw new MinigmaException("can only sync the same locks");
        }
        List<Certificate> otherCertificateList = otherLock.getCertificates();
        List<Certificate> thisCertificateList = getCertificates();
        otherCertificateList.addAll(thisCertificateList);
        for(Certificate certificate:otherCertificateList){
            if(!(thisCertificateList.contains(certificate))){

            }
        }
    }*/
    /**
     * Returns the first userID associated with this Lock. Note that Locks, like PGPPublicKeys, can
     * have multiple userIDs associated with them.
     * @return
     */
    public String getUserID(){
        return userIDs.get(0);
    }

    /**
     * Returns a List of all the text userIDs associated with this Lock.
     * @return
     */
    public List<String> getUserIDs(){
        return userIDs;
    }

    /**
     * @return the PGP 64-bit key ID encoded as an 8-character Base64 String.
    */
    public String getShortID(){
        return shortID;
    }
}<file_sep>/app/src/main/java/uk/co/platosys/minigma/utils/Kidney.java
/*
 * Copyright <NAME> and Platosys.
 ** (c) copyright 2018 Platosys
        * MIT Licence
        * Permission is hereby granted, free of charge, to any person obtaining a copy
        * of this software and associated documentation files (the "Software"), to deal
        * in the Software without restriction, including without limitation the rights
        * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        * copies of the Software, and to permit persons to whom the Software is
        * furnished to do so, subject to the following conditions:
        *
         *The above copyright notice and this permission notice shall be included in all
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
 */
package uk.co.platosys.minigma.utils;

import java.nio.ByteBuffer;
import java.nio.LongBuffer;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;


/**
 *Kidney is a utility providing static classes to convert a
 * PGP KeyID, or a fingerprint, to a string and back again.
 *
 * Additionally, it produces and expects strings which have colons, spaces or dashes at every
 * second character, to make the thing look prettier.
 *
 * The default separator is a dash, not a colon. The resultant strings therefore can  (but should not) be
 * used as xml attribute names.
 *
 */
public class Kidney {
    static final SecureRandom RANDOM=new SecureRandom();



    public static final char DEFAULT_SEPARATOR='-';
    static final char[] HEX_CHAR_TABLE = {
            '0', '1', '2', '3','4', '5', '6', '7','8', '9', 'a', 'b','c','d', 'e', 'f'
    };

    /**
     * Takes a long and returns a String, formatted as pairs of hexadecimal digits separated by dashes.
     * @param k
     * @return
     */
    public static String toString(long k){
        return toString(k, DEFAULT_SEPARATOR);
    }
    /**
     * takes a long and a char and returns a String, formatted as pairs of hexadecimal digits separated by the char.
     * @param k
     * @param separator
     * @return
     */
    public static String toString(long k, char separator){
        char[] sepchar = new char[1];
        sepchar[0]=separator;
        String unbrokenString = Long.toHexString(k);
        String brokenString="";
        for (int i=0; i<unbrokenString.length()-1; i=i+2){
            char [] x = new char[3];
            x[0] = unbrokenString.charAt(i);
            x[1] = unbrokenString.charAt(i+1);
            x[2] = sepchar[0];

            String newString = new String (x);
            brokenString = brokenString+newString;
        }
        if(brokenString.endsWith(new String(sepchar))){
            brokenString = brokenString.substring(0, (brokenString.length()-1));
        }
        return brokenString;
    }

    /**
     *
     * @param fingerprint
     * @return
     */
    public static String toString(byte[] fingerprint) {

        char separator='-';
        char[] hex = new char[(3 * fingerprint.length)-1];

        for (int i=0; i<((fingerprint.length)-1); i++) {
            byte b = fingerprint[i];
            int v = b & 0xFF;
            hex[(3*i)] = HEX_CHAR_TABLE[v >>> 4];
            hex[(3*i)+1]=HEX_CHAR_TABLE[v & 0xF];
            hex[(3*i)+2]=separator;
        }
        int i= (fingerprint.length-1);
        byte b = fingerprint[i];
        int v = b & 0xFF;
        hex[(3*i)] = HEX_CHAR_TABLE[v >>> 4];
        hex[(3*i)+1]=HEX_CHAR_TABLE[v & 0xF];

        return new String(hex);
    }


    /**
     *parses a hexadecimal string and returns it as a long.
     * @param hexString
     * @return
     * @throws NumberFormatException
     */
    public static long toLong(String hexString) throws NumberFormatException{
        long answer=0;
        StringBuffer strbuf = new StringBuffer();

        for (int i=0; i<hexString.length(); i++){
            char x = hexString.charAt(i);
            if ((x!=':')&&(x!=' ')&&(x!='-')){
                strbuf.append(x);
            }
        }
        String concatString = new String(strbuf);
        int len = concatString.length();
        for (int i=0; i<len; i++){
            int d=0;
            char x = concatString.charAt(len-(i+1));
            if (x=='0'){d=0;}
            else if (x=='1'){d=1;}
            else if (x=='2'){d=2;}
            else if (x=='3'){d=3;}
            else if (x=='4'){d=4;}
            else if (x=='5'){d=5;}
            else if (x=='6'){d=6;}
            else if (x=='7'){d=7;}
            else if (x=='8'){d=8;}
            else if (x=='9'){d=9;}
            else if ((x=='a')|(x=='A')){d=10;}
            else if ((x=='b')|(x=='B')){d=11;}
            else if ((x=='c')|(x=='C')){d=12;}
            else if ((x=='d')|(x=='D')){d=13;}
            else if ((x=='e')|(x=='E')){d=14;}
            else if ((x=='f')|(x=='F')){d=15;}

            else {
                throw new NumberFormatException(x+ " is not a recognised hex digit");
            }
            long col=1;
            for (int j=0; j<i; j++){
                col=col*16;
            }
            answer=answer+(col*d);

            //Log.d(TAG,5, "Kidney in: "+hexString+", out: "+Long.toHexString(answer));
        }
        return answer;
    }
    /**
     * returns a long as a byte array.
     * @param l
     * @return
     */
    public static byte[] longToByteArray(long l) {
        byte[] bArray = new byte[8];
        ByteBuffer bBuffer = ByteBuffer.wrap(bArray);
        LongBuffer lBuffer = bBuffer.asLongBuffer();
        lBuffer.put(0, l);
        return bArray;
    }

    /**
     * Returns a reasonably random long. As secure as the underlying implementation of SecureRandom, probably.
     * @return
     */
    public static long randomLong(){
        byte[] arr= new byte[8];
        RANDOM.nextBytes(arr);
        int i = 0;
        int len = 8;
        int cnt = 0;
        byte[] tmp = new byte[len];
        for (i = 0; i < len; i++) {
            tmp[cnt] = arr[i];
            cnt++;
        }
        long accum = 0;
        i = 0;
        for ( int shiftBy = 0; shiftBy < 32; shiftBy += 8 ) {
            accum |= ( (long)( tmp[i] & 0xff ) ) << shiftBy;
            i++;
        }
        return accum;
    }
}<file_sep>/app/src/main/java/uk/co/platosys/minigma/LockStore.java
package uk.co.platosys.minigma;
/* (c) copyright 2018 Platosys
        * MIT Licence
        * Permission is hereby granted, free of charge, to any person obtaining a copy
        * of this software and associated documentation files (the "Software"), to deal
        * in the Software without restriction, including without limitation the rights
        * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        * copies of the Software, and to permit persons to whom the Software is
        * furnished to do so, subject to the following conditions:
        *
        *The above copyright notice and this permission notice shall be included in all
        * copies or substantial portions of the Software.
        *
        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        * SOFTWARE.*/
import uk.co.platosys.minigma.exceptions.LockNotFoundException;
import uk.co.platosys.minigma.exceptions.MinigmaException;

import java.util.Iterator;

/**
 * This interface defines how Locks are stored. Minigma provides one implementation, MinigmaLockStore,
 * which uses PGPPublicKeyRings as a storage mechanism.
 *
 * Minigma does not use OpenPGP KeyIDs, but only fingerprints (the 160-bit timestamped hash of the public key)
 * OpenPGP short (32-bit) KeyIDs are broadly deprecated as it is now trivial to generate collisions, that is,
 * keys that have the same short keyID. Long (64-bit) keyIDs are much more secure, but collisions are theoretically
 * possible. Using the 160-bit fingerprint is less convenient if this is ever to be done humanly but Minigma is all about
 * doing this by machine.
 *
 */
public interface LockStore {
    /**
     * Adds a Lock to a Lockstore. If the Lockstore already contains a Lock with that id, it
     * is replaced (typically because the Lock's certification has changed).
     *
     * @param lock
     * @return
     */
    boolean addLock(Lock lock);

    /**
     * Removes the Lock with the given fingerprint from the Lockstore. If an implementation cannot remove
     * a Lock it should simply return false.
     * @param fingerprint
     * @return
     */
    boolean removeLock(Fingerprint fingerprint);

    /**
     * The most important method in the interface. All LockStores should implement this.
     * @param fingerprint
     * @return
     */
    Lock getLock(Fingerprint fingerprint) throws LockNotFoundException;


    Iterator<Lock> iterator() throws MinigmaException;

    /**
     * This method is used to retrieve a Lock from a Lockstore given a userID - typically an email
     * address, but any String identifier can be used.
     * @param userID
     * @return a Lock associated with this userID.
     * @throws MinigmaException
     * @throws LockNotFoundException if there is no Lock in the Lockstore having this userID.
     */
    Lock getLock(String userID)throws MinigmaException, LockNotFoundException;

    boolean contains(String userID);

    long getStoreId();

    String getUserID(Fingerprint keyID);
    String getUserID(long keyID);

    int getCount();//returns the number of keys held by  this Lockstore
}
<file_sep>/app/src/main/java/uk/co/platosys/minigma/exceptions/Exceptions.java
package uk.co.platosys.minigma.exceptions;

import android.util.Log;

public class Exceptions {
    public static void dump ( Throwable e) {
        Log.e("DUMP", "error", e);  //comment out to get testing to run.
        System.out.println(e.getClass().getName() + ":" + e.getMessage());
        if (e.getCause() != null) {

            System.out.println("Cause:"+e.getCause().getClass().getName());
            dump(e.getCause());
        } else {
            StackTraceElement[] stackTraceElements = e.getStackTrace();
            for (StackTraceElement stackTraceElement : stackTraceElements) {
                System.out.println(stackTraceElement.toString());
            }
        }
    }

    public static void dump (String TAG, Throwable e) {
        Log.e("DUMP", "error", e);  //comment out to get testing to run.
        System.out.println(e.getClass().getName() + ":" + e.getMessage());
        if (e.getCause() != null) {
            dump(TAG, e.getCause());
        } else {
            StackTraceElement[] stackTraceElements = e.getStackTrace();
            for (StackTraceElement stackTraceElement : stackTraceElements) {
                System.out.println(stackTraceElement.toString());
            }
        }
    }
    public static void dump (String TAG, String msg, Throwable e) {
        Log.e("DUMP", "error", e);  //comment out to get testing to run.
        System.out.println(msg);
        System.out.println(e.getClass().getName() + ":" + e.getMessage());
        if (e.getCause() != null) {
            dump(TAG, e.getCause());
        } else {
            StackTraceElement[] stackTraceElements = e.getStackTrace();
            for (StackTraceElement stackTraceElement : stackTraceElements) {
                System.out.println(stackTraceElement.toString());
            }
        }
    }
}

/*
switch




 */<file_sep>/app/src/main/java/uk/co/platosys/minigma/utils/MinigmaOutputStream.java
/* (c) copyright 2018 Platosys
        * MIT Licence
        * Permission is hereby granted, free of charge, to any person obtaining a copy
        * of this software and associated documentation files (the "Software"), to deal
        * in the Software without restriction, including without limitation the rights
        * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        * copies of the Software, and to permit persons to whom the Software is
        * furnished to do so, subject to the following conditions:
        *
        *The above copyright notice and this permission notice shall be included in all
        * copies or substantial portions of the Software.
        *
        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        * SOFTWARE.*/
package uk.co.platosys.minigma.utils;

import org.spongycastle.bcpg.ArmoredOutputStream;
import uk.co.platosys.minigma.Minigma;

import java.io.OutputStream;

/**MinigmaOutputStream is a PGP ArmoredOutputStream in which the headers identify Minigma
 * as the user agent
 *
 */
public class MinigmaOutputStream extends ArmoredOutputStream {
    public MinigmaOutputStream (OutputStream outputStream){
        super(outputStream);
        setHeader("Library", Minigma.LIBRARY_NAME);
        setHeader(ArmoredOutputStream.VERSION_HDR, Minigma.VERSION+", BC:"+Minigma.BCVERSION);
        setHeader("Comment", Minigma.APP_DESCRIPTION);
    }
}

<file_sep>/app/build.gradle
apply plugin: 'com.android.library'

android {
    compileSdkVersion 26
    defaultConfig {
         minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
repositories {
    mavenCentral()
    maven { url "https://jitpack.io" }
}

dependencies {
    implementation group: 'commons-codec', name: 'commons-codec', version: '1.14'
    implementation 'com.github.ejoftheweb:effwords:v0.2.2'
    implementation group: 'net.jpountz.lz4', name: 'lz4', version: '1.3.0'
    api group: 'com.madgag.spongycastle', name: 'prov', version: '1.58.0.0'
    api group: 'com.madgag.spongycastle', name: 'bcpg-jdk15on', version: '1.58.0.0'
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    testImplementation 'junit:junit:4.12'
    testImplementation 'org.mockito:mockito-core:3.2.0'
    testImplementation "org.powermock:powermock-module-junit4:1.7.4"
    testImplementation "org.powermock:powermock-api-mockito2:1.7.4"
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    implementation 'com.google.guava:guava:28.1-android'
    implementation 'com.android.volley:volley:1.1.1'
    implementation group: 'org.jdom', name: 'jdom2', version: '2.0.6'

}
<file_sep>/app/src/main/java/uk/co/platosys/minigma/android/package-info.java
/**This package contains Android-specific implementations of interfaces defined in the main package**/


package uk.co.platosys.minigma.android;<file_sep>/app/src/main/java/uk/co/platosys/minigma/VLock.java
package uk.co.platosys.minigma;


import uk.co.platosys.minigma.exceptions.MinigmaException;

/**
 * This extension of Lock provides a couple of additional fields
 * for use with Hagrid verifying keyservers.
 */


public class VLock extends Lock {
    private String token;
    private String email;
    public VLock(Lock lock) throws MinigmaException {
        super(lock.getBytes());
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
<file_sep>/README.md
# Minigmand
Minigmand is an implementation of Minigma for the Android platform. It replaces the BouncyCastle packages with the equivalent 
SpongyCastle ones (because of a naming conflict in Android). 

To use it, just put this in the repositories section of your gradle.build file:\n
maven { url "https://jitpack.io" } 
and in dependencies:
 implementation 'com.github.ejoftheweb:minigmand:master-SNAPSHOT'

 BUT: it is very much early dev code, it is certainly not production-ready. Feedback would of course be welcome.
 
 Usage.
 
 In Minigma, a public key is called a Lock and a private key is called a Key. You lock something with a Lock and you need a corresponding Key to unlock it. But they are all OpenPGP-compatible keys so you can use them in other OpenPGP applications (such as GPG). The idea behind Minigma is to have a really simple and logical API with the minimum learning curve so that you can use open crypto in other apps easily. YMMV of course! 
 
 to create a key-pair(a lockset): LockSmith.createLockSet(File keyDirectory, //where the generated secret Key is to be stored
                                               LockStore lockStore, //where the public Lock will be stored
                                               String username, //the username/email associated with the lockset
                                               char[] passPhrase, //the passphrase with which the generated private Key will be encrypted
                                               int algorithm)//use Algorithms.RSA
                                               
 The LockStore can be either a MinigmaLockStore, which is PGPublic Key Ring Collection implementation, stored as a base64 text file
on the local filesystem, or an HKPLockStore, which is an http: interface to a public keyserver. Or implement the interface yourself, which is probably best. 

For more info, see the wiki.

   
<file_sep>/app/src/main/java/uk/co/platosys/minigma/HKPLockStore.java
package uk.co.platosys.minigma;
/* (c) copyright 2018 Platosys
        * MIT Licence
        * Permission is hereby granted, free of charge, to any person obtaining a copy
        * of this software and associated documentation files (the "Software"), to deal
        * in the Software without restriction, including without limitation the rights
        * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        * copies of the Software, and to permit persons to whom the Software is
        * furnished to do so, subject to the following conditions:
        *
        *The above copyright notice and this permission notice shall be included in all
        * copies or substantial portions of the Software.
        *
        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        * SOFTWARE.*/
import android.util.Log;

import uk.co.platosys.minigma.exceptions.Exceptions;
import uk.co.platosys.minigma.exceptions.MinigmaException;
import uk.co.platosys.minigma.utils.Kidney;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;

import static java.net.HttpURLConnection.HTTP_ACCEPTED;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.net.HttpURLConnection.HTTP_OK;

/**This is an implementation of LockStore that uses public keyservers
 * as the backing store, with which it communicates using the HKP protocol
 * based on http.
 *
 * It is being updated to use the VKS protocol provided by the Hagrid verifying keyserver.
 *
 *
 */

public class HKPLockStore implements LockStore {

private String host;
private int port=11371; //this is the the default HKP port number
public static final String PROTOCOL="http:";
public static final String GET_FILE_PART="pks/lookup";
public static final String POST_FILE_PART="pks/add";
public static final String VKS_GET_BY_FINGERPRINT_FILE_PART="vks/v1/by-fingerprint";
public static final String VKS_GET_BY_KEYID_FILE_PART="vks/v1/by-keyid";
public static final String VKS_GET_BY_EMAIL_FILE_PART="vks/v1/by-email";
public static final String VKS_UPLOAD_FILE_PART="/vks/v1/upload";
public static final String VKS_REQUEST_VERIFY_FILE_PART="/vks/v1/request-verify";
public static final String VKS_MIMETYPE= "application/json";
public static final String ARMORED_PKEY_OPEN="-----BEGIN PGP PUBLIC KEY BLOCK-----";
public static final String ARMORED_PKEY_CLOSE="-----END PGP PUBLIC KEY BLOCK-----";
private boolean useVKS = false;

private String TAG = "HKPLockstore";

    /**
     *  Create an instance of the HKPLockStore by specifying a hostname and a port number to the constructor.
     *
     * @param host

     */
    public HKPLockStore(String host){
        this.host=host;

        //should  constructor verify host's existence? How?

    }

    /**
     *  Create an instance of the HKPLockStore by specifying a hostname and a port number to the constructor.
     *
     * @param host

     */
    public HKPLockStore(String host, int port){
        this.host=host;
        this.port=port;
        //should  constructor verify host's existence? How?

    }

    @Override
    public boolean addLock(Lock lock) {
        if (useVKS) {
            return addLockWithVKS(lock);
        } else {
            return addLockWithHKP(lock);
        }
    }

    private boolean addLockWithVKS (Lock lock){
            try {
                URL url = new URL(PROTOCOL, host, port, POST_FILE_PART);

                HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                httpURLConnection.setRequestMethod("POST");
                httpURLConnection.setDoOutput(true); //changes the default method to POST.
                httpURLConnection.addRequestProperty("op","post");
                httpURLConnection.addRequestProperty("options", "mr");
                OutputStream outputStream = new BufferedOutputStream( httpURLConnection.getOutputStream());
                outputStream.write(("keytext = ").getBytes());
                outputStream.write(lock.toArmoredString().getBytes());
                outputStream.flush();
                outputStream.close();
                int response = httpURLConnection.getResponseCode();
                switch (response){
                    case HTTP_OK:
                        return true;
                    case HTTP_ACCEPTED:
                        return true;
                    default:
                        handleError(response);

                }

                return false;
            }catch(Exception x){
                return false;
            }
        }


    private boolean addLockWithHKP(Lock lock){
        try {
            URL url = new URL(PROTOCOL, host, port, POST_FILE_PART);

            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setRequestMethod("POST");
            httpURLConnection.setDoOutput(true); //changes the default method to POST.
            httpURLConnection.addRequestProperty("op","post");
            httpURLConnection.addRequestProperty("options", "mr");
            OutputStream outputStream = new BufferedOutputStream( httpURLConnection.getOutputStream());
            outputStream.write(("keytext = ").getBytes());
            outputStream.write(lock.toArmoredString().getBytes());
            outputStream.flush();
            outputStream.close();
            int response = httpURLConnection.getResponseCode();
            switch (response){
                case HTTP_OK:
                    return true;
                case HTTP_ACCEPTED:
                    return true;
                default:
                    handleError(response);

            }

            return false;
        }catch(Exception x){
            return false;
        }
    }

    /** This method always returns false. It is not practicable (or for that matter usually ever desirable) to remove a public key from
     * a public keyserver.
     * @param fingerprint
     * @return always false*/
    @Override
    public boolean removeLock(Fingerprint fingerprint) {
        return false;
    }

    /**This method retrieves a Lock from the server given its keyID/fingerprint*/
    @Override
    public Lock getLock(Fingerprint fingerprint) {
        byte[] keyID = fingerprint.getFingerprintbytes();

        try {
            URL url = new URL(PROTOCOL, host, port, GET_FILE_PART);

            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setRequestMethod("GET");
            httpURLConnection.addRequestProperty("op","get");
            httpURLConnection.addRequestProperty("options", "mr");

            httpURLConnection.addRequestProperty("search", Kidney.toString(keyID));
            Log.d(TAG, url.getQuery());
            int responseCode = httpURLConnection.getResponseCode();
            switch (responseCode){
                case HTTP_OK:
                    return extractLock(httpURLConnection);
                //break;
                case HTTP_NOT_FOUND:
                    break;

                default:

            }
            return null;
        }catch(Exception x){
            Exceptions.dump(x);
            return null;
        }
    }

    @Override
    public Iterator<Lock> iterator() throws MinigmaException {
        return null;
    }

    /**This method retrieves a Lock from the server given a userID*/
    @Override
    public Lock getLock(String userID) throws MinigmaException {
        try {
            URL url = new URL(PROTOCOL, host, port, GET_FILE_PART);
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setRequestMethod("GET");
            httpURLConnection.addRequestProperty("op","get");
            httpURLConnection.addRequestProperty("options", "mr");
            httpURLConnection.addRequestProperty("search", userID);
            int responseCode = httpURLConnection.getResponseCode();
            switch (responseCode){
                case HTTP_OK:
                    return extractLock(httpURLConnection);
                    //break;
                case HTTP_NOT_FOUND:
                    break;
                default:

            }
        }catch (IOException iox){

        }
        return null;
    }

    private Lock extractLock(HttpURLConnection httpURLConnection){
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()));
            String inputLine;
            StringBuffer response = new StringBuffer();
            StringBuffer pgpKeysBlock = new StringBuffer();
            boolean pkey = false;
            while ((inputLine = bufferedReader.readLine()) != null) {
                if (inputLine.contains(ARMORED_PKEY_OPEN)){pkey=true;}
                if (pkey){pgpKeysBlock.append(inputLine);}
                response.append(inputLine);
                if (inputLine.contains(ARMORED_PKEY_CLOSE)){pkey=false;}
            }
            bufferedReader.close();
            Lock lock = new Lock(pgpKeysBlock.toString());
            return lock;
        }catch (IOException iox){
            //TODO
            Exceptions.dump(iox);
        }catch (MinigmaException mx) {
            //TODO
            Exceptions.dump(mx);
        }
        return null;
    }
    @Override
    public boolean contains(String userID) {
        try {
            return (getLock(userID) instanceof Lock);
        }catch (Exception x){
            return false;
        }
    }

    @Override
    public long getStoreId() {
        return 0;
    }

    @Override
    public String getUserID(Fingerprint fingerprint) {
        return null;
    }

    @Override
    public String getUserID(long keyID) {
        return null;
    }

    @Override
    public int getCount() {
        return 0;
    }

    public void setPort(int port){
        this.port=port;
    }

    public boolean isUseVKS(){return useVKS;}
    public boolean setUseVKS(boolean useVKS){
        this.useVKS=useVKS;
        return true;
    }
    private void handleError(int response){
        Log.d(TAG, "HTTP error code:"+response);
    }
}

<file_sep>/app/src/main/java/uk/co/platosys/minigma/utils/package-info.java
/**
 * Utilities for handling Base64 streams etc.
 */
package uk.co.platosys.minigma.utils;<file_sep>/app/src/main/java/uk/co/platosys/minigma/BigBinary.java
package uk.co.platosys.minigma;

//import android.support.annotation.NonNull;

import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;

import java.math.BigInteger;
import java.text.ParseException;

import uk.co.platosys.minigma.utils.MinigmaUtils;

/**In crypto we use big binary numbers a lot. Often, the java.math class BigInteger will suffice,
 * or we can just handle the underlying byte arrays. BigBinary is a wrapper for a byte[] and includes
 * methods for instantiating from, and returning the underlying number as, a Base64 String which is often the most
 * practical way of handling it.
 *
 * BigBinary was introduced in Minigma v0.2, replacing the earlier use of Strings as digests etc. There's
 * thus less need for Base64 coding and decoding under the hood - because BigBinary is a byte array in an Object -
 * and it avoids any confusion with cleartext semantic Strings.
 *
 */
public class BigBinary implements Comparable{
    private byte[] bytes;
    public BigBinary (String string)throws ParseException {
        this.bytes= MinigmaUtils.decode(string);
    }
    public BigBinary(byte[] bytes){
        this.bytes=bytes;
    }
    @Override
    public String toString(){
        return MinigmaUtils.encode(bytes, true);
    }
    public byte[] toByteArray(){
        return bytes;
    }
    public int getBitlength(){
        return bytes.length*8;
    }

    public BigInteger toBigInteger() {return new BigInteger(bytes);}
    //Append methods
    /**
     * Appends the given integer to this BigBinary. Note this is not the same as addition, it is basically
     *  multiplying by 2^32 and then adding.
     * @param annex
     * @return this BigBinary with the annex appended.
     */
    public BigBinary append(int annex){
        byte[]addbytes = Ints.toByteArray(annex);
        return append(addbytes);
    }

    public BigBinary append (long annex){
        byte[] addbytes = Longs.toByteArray(annex);
        return append(addbytes);
    }

    public BigBinary append(byte[] annex){
        byte[] newArray = new byte[bytes.length+annex.length];
        int a=0;
        for (int i=0; i<bytes.length; i++){
            newArray[a]=bytes[i];
            a++;
        }
        for (int i=0; i<annex.length; i++){
            newArray[a]=annex[i];
            a++;
        }
        this.bytes=newArray;
        return this;
    }
    //Detach methods
    /** The detach methods are the inverses of the append methods
     * This method detaches a byte array of length length from the
     * underlying byte array.
     */
    public byte[] detach(int length) throws ArrayIndexOutOfBoundsException {
        if(length>bytes.length){throw new ArrayIndexOutOfBoundsException("attempting to detach too much");}

        byte[] detached = new byte[length];
        byte[] remains = new byte[bytes.length-length];
        for (int i=0; i<remains.length; i++){
            remains[i]=bytes[i];
        }
        for (int i=remains.length; i<bytes.length; i++){
            detached[i]=bytes[i];
        }
        this.bytes=remains;
        return detached;
    }
    public long detachLong() throws ArrayIndexOutOfBoundsException {
        byte[] detached = detach( Longs.BYTES);
        return Longs.fromByteArray(detached);
    }
    public int detachInt() throws ArrayIndexOutOfBoundsException {
        byte[] detached = detach(Ints.BYTES);
        return Ints.fromByteArray(detached);
    }
    @Override
    public boolean equals (Object object){
        if (object instanceof BigBinary){
            BigBinary bigBinary = (BigBinary) object;
            byte[] theirs = bigBinary.toByteArray();
            if (theirs.length != bytes.length) {return false;}
            for (int i=0; i<bytes.length; i++){
                if (bytes[i]!=theirs[i]){return false;}
            }
            return true;
        }else{
            return false;
        }
    }
    @Override
    public int compareTo( Object object) {
        if (object instanceof BigBinary){
            BigBinary bigBinary = (BigBinary) object;
            byte[] theirs = bigBinary.toByteArray();
            if (theirs.length != bytes.length) {
                //this behaviour isn't quite right. But it will do for now.
                //TODO fixit.
                throw new ClassCastException("comparing unequal bitlength BigBinaries");
            }
            for (int i=0; i<bytes.length; i++){
                if (bytes[i]>theirs[i]) {return 1;}
                if (bytes[i]<theirs[i]) {return -1;}

            }
            return 0;//we've gone through the whole array and they're all equal.
        }else{
            throw new ClassCastException();
        }
    }
}
<file_sep>/app/src/main/java/uk/co/platosys/minigma/PassPhraser.java
package uk.co.platosys.minigma;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;

import uk.co.platosys.effwords.Effwords;
import uk.co.platosys.minigma.exceptions.Exceptions;

/**Class to generate random-word passphrases.
 *
 * In  Minigma, a Key needs a passphrase, which is a char array.
 *
 *      PassPhraser generates random-word passphrases. There is some evidence that random-word
 *      passphrases are easier to remember for a similar level of entropy than random-character ones,
 *      even though the resulting passphrase is much longer.
 *
 *      Random passphrases are known to be more secure than human-generated ones.
 *
 *      You can specify alternative wordlists supported by Effwords. At the moment, Effwords only
 *      supports the three EFF lists which are English words. It would be good to support other languages
 *      though.
 *
 *  */
public class PassPhraser {

    private  File wordListFile;
    private List<String> wordList;
    public static final String WORDSEPARATOR = " ";
    public static final char WORDSEPARATOR_CHAR=' ';
    public static int LONGWORDLIST=Effwords.EFF_LONGLIST;
    public static int SHORTWORDLIST=Effwords.EFF_SHORTLIST;
    public static int ALTWORDLIST=Effwords.EFF_DEFAULTLIST;


    /**
     * Returns a random word passphrase of length words from the default
     * word list, using a SecureRandom instance for entropy.
     * @param words
     * @return
     */
    public static char[] getPassPhrase(int words) {
        return getPassPhrase(LONGWORDLIST, words);
    }
    /**
     * Returns a random word passphrase of length words from the given
     * wordlist, using a SecureRandom instance for entropy.
     * @param words
     * @return
     */
    public static char[] getPassPhrase(int wordList, int words) {
        SecureRandom secureRandom = new SecureRandom();
        StringBuffer buffer= new StringBuffer();
        for (int i = 0; i < words; i++) {
            try {
                int word = secureRandom.nextInt();
                if (i > 0) {
                    buffer.append(WORDSEPARATOR);
                }
                buffer.append(Effwords.getWord(wordList,word));
            }catch (Exception x) {
                Exceptions.dump(x);
            }
        }
        return buffer.toString().toCharArray();
    }

    /**
     * converts a char array to an array of strings.
     * @param passphrase
     * @return
     */
    public static  List<String> toWordList(char[] passphrase) {
        ArrayList<String> words = new ArrayList<>();
        StringBuffer stringBuffer = new StringBuffer();
        for (char ch : passphrase) {
            if (ch != WORDSEPARATOR_CHAR) {
                stringBuffer.append(ch);
            } else {
                String word = stringBuffer.toString();
                words.add(word);
                stringBuffer = new StringBuffer();
            }
        }
        if (stringBuffer != null) {
            words.add(stringBuffer.toString());
        }
        return words;
    }

    /**
     * returns a word from the given wordlist, where word is a random integer
     * such as one generated by the user throwing dice.
     * @param wordlist the Effwords wordlist selected.
     * @param word a random integer
     * @return
     */
    public static String getWord(int wordlist, int word){
        try {
            return Effwords.getWord(wordlist, word);
        }catch(Exception x){
            Exceptions.dump(x);
            return null;
        }
    }

}

<file_sep>/app/src/main/java/uk/co/platosys/minigma/Key.java
package uk.co.platosys.minigma;
/*
Copyright (C) 2017 <NAME> and Platosys

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
 is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL EDWARD BARROW OR
PLATOSYS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 */





import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.spongycastle.bcpg.ArmoredInputStream;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSecretKeyRingCollection;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.spongycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;

import uk.co.platosys.minigma.exceptions.BadPassphraseException;
import uk.co.platosys.minigma.exceptions.MinigmaException;
import uk.co.platosys.minigma.exceptions.MinigmaOtherException;
import uk.co.platosys.minigma.exceptions.NoDecryptionKeyException;
import uk.co.platosys.minigma.utils.Kidney;
import uk.co.platosys.minigma.utils.MinigmaOutputStream;
import uk.co.platosys.minigma.utils.MinigmaUtils;


/**
 * In Minigma, a Key is the object used to unlock something that has been locked
 * with a corresponding Lock. Minigma Keys and Locks correspond to private keys and
 * public keys respectively in other asymmetric crypto systems.
 *
 * Minigma is a fairly lightweight wrapper to OpenPGP, so a Minigma Key can be instantiated
 * from OpenPGP private key material.
 *
 * HOWEVER: OpenPGP private key material does not include the UserID. The KeyID or fingerprint can be deduced from the
 * key material, but not the relevant userID (which is, generally, an email address). Therefore, you need to look up the
 * associated userID from a keyring, or in Minigma, a Lockstore, using lockstore.getUserId(long keyid) every time you use the
 * key for signing.
 * Minigma therefore provides a set of overloaded constructors which take a LockStore argument which should be used for creating
 * signing keys.
 *
 * A Key always needs a passphrase.
 * @author edward
 *
 *
 *
 */
public class Key {

    private PGPSecretKey signingKey;
    private PGPSecretKey masterKey;
    private PGPSecretKeyRingCollection secretKeyRingCollection;
    private Fingerprint fingerprint;
    private String userID="";
    private File file;


    /** @param secretKeyRingCollection
     */
    protected Key(PGPSecretKeyRingCollection secretKeyRingCollection) throws Exception{
        this.secretKeyRingCollection=secretKeyRingCollection;
        init(null);
    }


    /** @param keyFile  a java.io.File object pointing to  a text file of OpenPGP key material
     *                   Keys instantiated using this method will not be able to return a meaningful
     *                  userID
     */
    public Key(File keyFile)throws MinigmaException {
        try{
            this.file=keyFile;
            FileInputStream fileStream=new FileInputStream(keyFile);
            InputStream instream=new ArmoredInputStream(fileStream);
            instream=PGPUtil.getDecoderStream(instream);
            KeyFingerPrintCalculator kfpc = new BcKeyFingerprintCalculator();
            this.secretKeyRingCollection = new PGPSecretKeyRingCollection(instream, kfpc);
            init(null);
            instream.close();
            fileStream.close();
        }catch(Exception x){
            throw new MinigmaException("problem loading Key from file", x);
        }
    }

    /**Keys can also be instantiated from an InputStream rather than a File. (although why??)
     *
     * @param inputStream
     * @throws MinigmaException
     */
    @Deprecated
    public Key(InputStream inputStream)throws MinigmaException {
        try{
            InputStream instream=new ArmoredInputStream(inputStream);
            instream=PGPUtil.getDecoderStream(instream);
            KeyFingerPrintCalculator kfpc = new BcKeyFingerprintCalculator();
            this.secretKeyRingCollection = new PGPSecretKeyRingCollection(instream, kfpc);
            init(null);
            instream.close();
            inputStream.close();
        }catch(Exception x){
            throw new MinigmaException("problem loading Key from input stream", x);
        }
    }
    /** @param keyFile  a java.io.File object pointing to  a text file of OpenPGP key material. This constructor
     *                   takes a LockStore argument, to enable lookup of a meaningful userID
     */

    public Key(File keyFile, LockStore lockStore)throws MinigmaException {
        try{
            this.file=keyFile;
            FileInputStream fileStream=new FileInputStream(keyFile);
            InputStream instream=new ArmoredInputStream(fileStream);
            instream=PGPUtil.getDecoderStream(instream);
            KeyFingerPrintCalculator kfpc = new BcKeyFingerprintCalculator();
            this.secretKeyRingCollection = new PGPSecretKeyRingCollection(instream, kfpc);
            init(lockStore);
            instream.close();
            fileStream.close();
        }catch(Exception x){
            throw new MinigmaException("problem loading Key from file", x);
        }
    }
    @Deprecated
    public Key(InputStream inputStream, LockStore lockStore)throws MinigmaException {
        try{
            InputStream instream=new ArmoredInputStream(inputStream);
            instream=PGPUtil.getDecoderStream(instream);
            KeyFingerPrintCalculator kfpc = new BcKeyFingerprintCalculator();
            this.secretKeyRingCollection = new PGPSecretKeyRingCollection(instream, kfpc);
            init(lockStore);
            instream.close();
            inputStream.close();
        }catch(Exception x){
            throw new MinigmaException("problem loading Key from input stream", x);
        }
    }
    private void init(LockStore lockStore) throws Exception{
        try{
            signingKey = null;
            masterKey=null;
            //decryptionKey = null;
            Iterator<PGPSecretKeyRing> ringIterator = secretKeyRingCollection.getKeyRings();
            while ((signingKey == null) && ringIterator.hasNext()){
                PGPSecretKeyRing  pgpSecretKeyRing = ringIterator.next();
                Iterator<PGPSecretKey> keyIterator = pgpSecretKeyRing.getSecretKeys();
                while ((signingKey == null) && keyIterator.hasNext()){
                    PGPSecretKey key = keyIterator.next();
                    if (key.isSigningKey()){
                        signingKey = key;
                        fingerprint = new Fingerprint(signingKey.getPublicKey().getFingerprint());
                        if(lockStore!=null){
                            this.userID=lockStore.getUserID(fingerprint);
                        }
                    }else if (key.isMasterKey()){
                        masterKey=key;
                    }
                }
            }
            if (signingKey == null) {
                throw new IllegalArgumentException("Can't find signing key in key ring.");
            }
        }catch(Exception e){
            throw e;
        }
    }
    /** @return the Fingerprint object associated with this Key;*/
    public Fingerprint getFingerprint(){
        return fingerprint;
    }
    /** @return the 64-bit keyID associated with this Key. */
    public long getKeyID(){
        return fingerprint.getKeyID();
    }
    /** @return the primary userID associated with this key;
     * or the empty string */
    public String getUserID() {return userID;}

    /**
     *
     * @return
     */
    protected PGPSecretKey getSigningKey(){return signingKey;}
    protected PGPSecretKey getMasterKey(){return masterKey;}

    /**
     * Changes the passphrase of this Key.
     */
    public void changePassphrase(char[] oldpassphrase, char[] newpassphrase) throws MinigmaOtherException, BadPassphraseException {
        PBESecretKeyDecryptor decryptor=null;
        PBESecretKeyEncryptor encryptor=null;
        List<PGPSecretKeyRing> newkeys = new ArrayList<>();
        try {
            decryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build(oldpassphrase);
            encryptor = new JcePBESecretKeyEncryptorBuilder(Algorithms.SYMMETRIC_ALGORITHM).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(newpassphrase);
        }catch(PGPException px){
            throw new MinigmaOtherException("error creating passphrase encryptor/decryptor", px);
        }
        Iterator<PGPSecretKeyRing> ringIterator = secretKeyRingCollection.iterator(); //TODO
        while(ringIterator.hasNext()){
            PGPSecretKeyRing secretKeyRing = ringIterator.next();
            try {
                newkeys.add(PGPSecretKeyRing.copyWithNewPassword(secretKeyRing, decryptor, encryptor));
            }catch(PGPException px){
                throw new BadPassphraseException("probably", px);
            }
        }
        try {
            MinigmaOutputStream keyOut = new  MinigmaOutputStream(new FileOutputStream(file));
            secretKeyRingCollection = new PGPSecretKeyRingCollection(newkeys);
            secretKeyRingCollection.encode(keyOut);
            keyOut.flush();
            keyOut.close();
        }catch(IOException iox){
            //TODO
        }catch (PGPException px){
            //TODO
        }
    }

    /**
     *Returns an BouncyCastle PGPSecretKey decryption key, to be used to
     * decrypt/unlock something.
     * @param keyID
     * @return
     */
    protected PGPSecretKey getDecryptionKey(long keyID) throws MinigmaException, NoDecryptionKeyException{
        try{
            if (secretKeyRingCollection.contains(keyID)) {
                return secretKeyRingCollection.getSecretKey(keyID);
            }else{
                throw new NoDecryptionKeyException("Key does not decrypt key with id:"+ Kidney.toString(keyID));
            }
        }catch(NoDecryptionKeyException ndke) {
            throw ndke;
        }catch(Exception e){
            throw new MinigmaException("Key-getDecryptionKey exception", e);
        }
    }

    /**
     * @param toBeSigned the binary data to be signed in the form of a byte array
     * @param passphrase
     * @return a Signature object
     * @throws MinigmaException
     */
    public Signature sign(byte[]  toBeSigned, char[] passphrase) throws BadPassphraseException, MinigmaOtherException {
        BigBinary digest= Digester.digest(toBeSigned);
        return SignatureEngine.sign(digest, this, passphrase);
    }

    /**
     * @param toBeSigned the BigBinary to be signed
     * @param passphrase
     * @return a Signature object
     * @throws MinigmaException
     */
    public Signature sign(BigBinary  toBeSigned, char[] passphrase) throws BadPassphraseException, MinigmaOtherException{
        BigBinary digest= Digester.digest(toBeSigned);
        return SignatureEngine.sign(digest, this, passphrase);
    }

    /**
     * @param toBeSigned the String to be signed
     * @param passphrase
     * @return a Signature object.
     * @throws MinigmaException
     */
    public Signature sign(String toBeSigned, char[] passphrase) throws BadPassphraseException, MinigmaOtherException{
        BigBinary digest= Digester.digest(toBeSigned);
        return SignatureEngine.sign(digest, this, passphrase);
    }

    /**
     * @param toBeSigned the String to be signed
     * @param passphrase
     * @param notations a List of Notation objects to be included in this signature (as PGPNotationData)
     * @return a Signature object
     * @throws MinigmaException
     */
    public Signature sign(byte[] toBeSigned, List<Notation> notations, char[] passphrase) throws BadPassphraseException, MinigmaOtherException{
        BigBinary digest= Digester.digest(toBeSigned);
        return SignatureEngine.sign(digest, this, notations, passphrase);
    }

    /**
     * @param toBeSigned the String to be signed
     * @param passphrase
     * @param notations a List of Notation objects to be included in this signature (as PGPNotationData)
     * @return a Signature object.
     * @throws MinigmaException
     */
    public Signature sign(String toBeSigned, List<Notation> notations, char[] passphrase) throws BadPassphraseException, MinigmaOtherException{
        BigBinary digest= Digester.digest(toBeSigned);
        return SignatureEngine.sign(digest, this, notations, passphrase);
    }

    /**
     * This takes ciphertext and returns the cleartext. The ciphertext is actually Base64-encoded binary data.
     *
     * @param ciphertext to be unlocked
     * @param passphrase This key's passphrase
     * @return a cleartext String
     * @throws Exception
     */
    public String unlock(String ciphertext, char[] passphrase) throws BadPassphraseException, MinigmaOtherException{
        return unlockAsString(MinigmaUtils.decode(ciphertext),passphrase);
    }
    public String unlockAsString(byte[] bytes, char[] passphrase) throws BadPassphraseException, MinigmaOtherException{
        try {
            return new String(unlockAsBytes(bytes, passphrase), "UTF-8");
        }catch (UnsupportedEncodingException uex){
            //we specify "UTF-8", this exception should never be thrown.
            throw new MinigmaOtherException("coding unsupported", uex);
        }
    }
    public byte[] unlockAsBytes
            (byte[] bytes, char[] passphrase) throws BadPassphraseException, MinigmaOtherException {
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        return CryptoEngine.decrypt(bais, this, passphrase);

    }
    public BigBinary unlock(BigBinary cipherbytes, char[] passphrase) throws BadPassphraseException, MinigmaOtherException {
        return new BigBinary(unlockAsBytes(cipherbytes.toByteArray(),passphrase));
    }

}
<file_sep>/app/src/main/java/uk/co/platosys/minigma/Digester.java
/*
 * Copyright <NAME> and Platosys.
 * This software is licensed under the Free Software Foundation's
General Public Licence, version 2 ("the GPL").
The full terms of the licence can be found online at http://www.fsf.org/

In brief, you are free to copy and to modify the code in any way you wish, but if you
publish the modified code you may only do so under the GPL, and (if asked) you must
 supply a copy of the source code alongside any compiled code.

Platosys software can also be licensed on negotiated terms if the GPL is inappropriate.
For further information about this, please contact <EMAIL>
 */

package uk.co.platosys.minigma;



/* (c) copyright 2018 Platosys
        * MIT Licence
        * Permission is hereby granted, free of charge, to any person obtaining a copy
        * of this software and associated documentation files (the "Software"), to deal
        * in the Software without restriction, including without limitation the rights
        * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        * copies of the Software, and to permit persons to whom the Software is
        * furnished to do so, subject to the following conditions:
        *
        *The above copyright notice and this permission notice shall be included in all
        * copies or substantial portions of the Software.
        *
        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        * SOFTWARE.*/
import java.io.IOException;
import java.nio.charset.Charset;

import net.jpountz.xxhash.XXHash64;
import net.jpountz.xxhash.XXHashFactory;
import org.spongycastle.crypto.digests.KeccakDigest;
import org.spongycastle.crypto.digests.SHA3Digest;
import org.spongycastle.openpgp.PGPSignature;
import uk.co.platosys.minigma.exceptions.Exceptions;
import uk.co.platosys.minigma.exceptions.MinigmaException;
import uk.co.platosys.minigma.exceptions.MinigmaOtherException;
import uk.co.platosys.minigma.utils.MinigmaUtils;


/**
 * a class with static methods for digesting Strings, BigBinaries and
 * byte arrays.
 * @author edward
 */
public class Digester {
    private static String TAG ="Digester";

    public static BigBinary digest (String string) throws MinigmaOtherException{
        return digest(string.getBytes(Charset.forName("UTF-8")));
    }

    /**
     * Returns a BigBinary digest of the supplied BigBinary.
     * @param bigBinary
     * @return
     * @throws MinigmaException
     */
    public static BigBinary digest (BigBinary bigBinary) throws MinigmaOtherException {
        return digest(bigBinary.toByteArray());
    }
    /**Takes a byte array and returns a BigBinary. Uses SHA3-256 as the digest
     * algorithm
     *
     * */

    public static BigBinary digest (byte[] bytes) throws MinigmaOtherException{
        try{
            KeccakDigest digest = new SHA3Digest(256);
            for(byte byt:bytes){
                digest.update(byt);
            }
            byte[] digested = new byte[digest.getDigestSize()];
            digest.doFinal(digested, 0);
            return new BigBinary(digested);
        }catch(Exception e){
            throw new MinigmaOtherException("error making digest", e);
        }
    }

    /**
     * This returns a short String which is a non-cryptographic hash of
     * the supplied byte array. The short hashes so obtained are used as identifiers
     * and filenames for Signatures.
     * @param bytes
     * @return
     * @throws MinigmaException
     */
    public static String shortDigest (byte[] bytes) {
        try{ XXHashFactory xxHashFactory = XXHashFactory.fastestInstance();
            XXHash64 xxHash64 = xxHashFactory.hash64();
            long longHash = xxHash64.hash(bytes, 0, 0, 0);
            return(MinigmaUtils.encode(longHash));
        }catch(Exception e){
            Exceptions.dump(e);
            return null;
        }
    }
    public static String shortDigest (PGPSignature signature) {
        try {
            return shortDigest(signature.getEncoded());
        }catch(IOException iox){
            Exceptions.dump(iox);
            return null;
        }
    }

}

<file_sep>/app/src/main/java/uk/co/platosys/minigma/exceptions/BadPassphraseException.java
package uk.co.platosys.minigma.exceptions;

public class BadPassphraseException extends MinigmaException {
    public BadPassphraseException(String msg){
        super(msg);
    }
    public BadPassphraseException(String msg, Throwable cause){
        super(msg, cause);
    }
}
<file_sep>/app/src/main/java/uk/co/platosys/minigma/BaseSignature.java
package uk.co.platosys.minigma;
/* (c) copyright 2018 Platosys
        * MIT Licence
        * Permission is hereby granted, free of charge, to any person obtaining a copy
        * of this software and associated documentation files (the "Software"), to deal
        * in the Software without restriction, including without limitation the rights
        * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        * copies of the Software, and to permit persons to whom the Software is
        * furnished to do so, subject to the following conditions:
        *
        *The above copyright notice and this permission notice shall be included in all
        * copies or substantial portions of the Software.
        *
        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        * SOFTWARE.*/
import org.spongycastle.bcpg.ArmoredInputStream;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.sig.NotationData;
import org.spongycastle.openpgp.*;
import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;
import uk.co.platosys.minigma.exceptions.MinigmaException;
import uk.co.platosys.minigma.utils.MinigmaOutputStream;
import uk.co.platosys.minigma.utils.MinigmaUtils;

import java.io.*;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Abstract base class wrapping PGPSignature objects
 */
public abstract class BaseSignature {
    protected PGPSignature pgpSignature;
    protected String shortDigest;


    /**
     * Instantiates a Minigma Signature object from a BouncyCastle PGPSignature object
     * @param pgpSignature
     */
    protected BaseSignature (PGPSignature pgpSignature){
        this.pgpSignature=pgpSignature;
        this.shortDigest=Digester.shortDigest(pgpSignature);

    }

    /**Instantiates a Minigma Signature object given a suitably-encoded String
     * @param string a Base64-encoded String
     * @throws ParseException if the supplied String contains wrong characters.
     */
    protected BaseSignature (String string) throws ParseException {
        this (new BigBinary(string));
    }

    /**Instantiates a Minigma Signature object given a BigBinary object.
     * @param bigBinary the signature as a BigBinary object.
     */
    protected BaseSignature (BigBinary bigBinary){
        this(new ByteArrayInputStream(bigBinary.toByteArray()));

    }
    protected BaseSignature (InputStream inputStream){
        PGPSignatureList signatureList;
        try {
            ArmoredInputStream armoredInputStream = new ArmoredInputStream(inputStream);
            JcaPGPObjectFactory jcaPGPObjectFactory = new JcaPGPObjectFactory(PGPUtil.getDecoderStream(armoredInputStream));
            Object object = jcaPGPObjectFactory.nextObject();
            if (object instanceof PGPCompressedData) {
                PGPCompressedData pgpCompressedData = (PGPCompressedData) object;
                jcaPGPObjectFactory = new JcaPGPObjectFactory(pgpCompressedData.getDataStream());
                Object object2 = jcaPGPObjectFactory.nextObject();
                if (object2 instanceof PGPSignatureList) {
                    signatureList = (PGPSignatureList) object2;
                } else {
                    throw new MinigmaException("unexpected object type found in compressed data signature stream");
                }
            } else if (object instanceof PGPSignatureList) {
                signatureList = (PGPSignatureList) object;
            } else {
                throw new MinigmaException("unexpected object type found in uncompressed signature stream");
            }
            this.pgpSignature=signatureList.get(0);
            this.shortDigest=Digester.shortDigest(pgpSignature);
        }catch(Exception x){

        }
    }
    /**
     * Returns the Signature as a String. The String representations don't have PGP Ascii Armor so aren't fully interoperable,
     * if you need Ascii Armor, use the following method with armored=true.
     * @return
     */
    public String encodeToString(){return encodeToString(false);}

    public String encodeToString(boolean armored){
        return MinigmaUtils.encode(encodeToBytes(armored));
    }
    protected byte[] encodeToBytes (boolean armored){
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try {
            encodeToStream(byteArrayOutputStream, armored);
        }catch(Exception x){}
        byte[] signatureBytes=byteArrayOutputStream.toByteArray();
        return signatureBytes;
    }
    protected byte[] getBytes(){
        return encodeToBytes(false);
    }

    protected BigBinary getBigBinary() {return new BigBinary(encodeToBytes(false));}

    /**
     * Writes the signature to the given output stream, with or without PGP Ascii Armor headers/footers.
     * Use armored=false if interoperability isn't a concern.
     *
     * @param outputStream
     * @param armored
     * @throws IOException
     */
    public void encodeToStream(OutputStream outputStream, boolean armored) throws IOException{
        if(armored){
            encodeToStream(outputStream);
        }else{
            pgpSignature.encode(outputStream);
            outputStream.flush();
            outputStream.close();
        }
    }

    /**
     * Writes the signature to the given output stream in PGP AsciiArmored format. This maximises interoperability with
     * other OpenPGP implementations.
     * @param outputStream
     * @throws IOException
     */
    public void encodeToStream(OutputStream outputStream) throws IOException{
        ArmoredOutputStream armoredOutputStream = new MinigmaOutputStream(outputStream);
        pgpSignature.encode(armoredOutputStream);
        armoredOutputStream.flush();
        armoredOutputStream.close();
    }

    /**
     * Writes the signature to the given file in PGP Ascii Armored format. This maximises interoperability with
     * other OpenPGP implementations.
     * @param file
     * @throws IOException
     */
    public void encodeToFile(File file) throws  IOException{
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        encodeToStream(fileOutputStream);
        fileOutputStream.flush();
        fileOutputStream.close();
    }

    /**
     * The short digest is a Minigma extension to the OpenPGP standard and returns
     * a non-cryptographic short digest which can be used, for example, as a filename for the signature
     * itself. The short digest uses a different fast hashing algorithm. It's not a digest of
     * the material being signed but of the signature itself.  It's not easily reversible, but it's not
     * demonstrably hard either, nor is it guaranteed to be collision-free, so it should only be used where
     * the consequences of either a collision or of someone managing to deduce the original from the digest
     * are manageable.
     * @return a short digest of the Signature object.
     */
    public String getShortDigest(){
        return shortDigest;
    }

    /**
     * Returns the ID of the key that signed this signature, as a long. Note that this is a 64-bit keyID, and not a 160-bit fingerprint.
     * Bear in mind that collisions (an ID identifying a different key) are somewhat less unlikely with
     * 64-bit IDs, which is why current best PGP practice is to use fingerprints rather than keyIDs. But what is likely
     * to happen? You get the keyID, use it to look up a corresponding Lock (public key), and if it's the wrong
     * one, the signature won't verify. Don't associate an actual person with a signature until it is properly
     * verified.
     * @return
     */
    public long getKeyID(){
        return pgpSignature.getKeyID();
    }


    @Override
    public boolean equals(Object object){
        if (object instanceof BaseSignature){
            BaseSignature baseSignature = (BaseSignature) object;
            return Arrays.equals(getBytes(),baseSignature.getBytes());
        }else{
            return false;
        }
     }

    /**OpenPGP allows Signatures to carry NotationData, which is an extensible, user-defined
     * vehicle for attaching additional information to a signature. Minigma specifically uses
     * name-value pairs for this (the mechanism also allows for binary NotationData, which is
     * not currently supported under Minigma).
     * @return List of Notation objects.
     */
    public List<Notation> getNotations(){
        List<Notation> notations = new ArrayList<>();
        PGPSignatureSubpacketVector notationVector = pgpSignature.getHashedSubPackets();
        NotationData[] notationData = notationVector.getNotationDataOccurrences();
        for(NotationData notationD:notationData){
            Notation notation = new Notation(notationD.getNotationName(), notationD.getNotationValue());
            notation.setCritical(notationD.isCritical());
            notation.setHumanReadable(notationD.isHumanReadable());
            notations.add(notation);
        }
        return notations;
    }
    protected PGPSignature getPgpSignature(){
        return pgpSignature;
    }
    public int getHashAlgorithm(){
            return pgpSignature.getHashAlgorithm();
    }
    public int getKeyAlgorithm(){
            return pgpSignature.getKeyAlgorithm();
    }
    protected  int getSignatureType(){
        return pgpSignature.getSignatureType();
    }
}
<file_sep>/app/src/main/java/uk/co/platosys/minigma/exceptions/MinigmaOtherException.java
package uk.co.platosys.minigma.exceptions;

public class MinigmaOtherException extends MinigmaException {
    public MinigmaOtherException(String msg){
        super(msg);
    }
    public MinigmaOtherException(String msg, Throwable cause){
        super(msg, cause);
    }
}
<file_sep>/app/src/main/java/uk/co/platosys/minigma/package-info.java
/**
 * Minigma is an intuitive OpenPGP API, using BouncyCastle under the skin. It is not fully OpenPGP
 * compatible (it can't handle all OpenPGP's algorithms) but a compliant OpenPGP app should be able to
 * decrypt Minigma-encrypted cyphertext.
 */
package uk.co.platosys.minigma;
<file_sep>/app/src/main/java/uk/co/platosys/minigma/exceptions/InvalidXMLException.java
package uk.co.platosys.minigma.exceptions;
/**Exception thrown when invalid/badly-formed XML is produced by the XMLUtils class*/
public class InvalidXMLException extends Exception {

        public InvalidXMLException (String msg){
            super(msg);

        }
        public InvalidXMLException (String msg, Throwable cause){
            super(msg, cause);

        }

}
<file_sep>/app/src/main/java/uk/co/platosys/minigma/exceptions/package-info.java
/**
 * Exceptions thrown by the Minigma packages
 */
package uk.co.platosys.minigma.exceptions;<file_sep>/app/src/main/java/uk/co/platosys/minigma/CryptoEngine.java

package uk.co.platosys.minigma;

/* (c) copyright 2018 Platosys
        * MIT Licence
        * Permission is hereby granted, free of charge, to any person obtaining a copy
        * of this software and associated documentation files (the "Software"), to deal
        * in the Software without restriction, including without limitation the rights
        * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        * copies of the Software, and to permit persons to whom the Software is
        * furnished to do so, subject to the following conditions:
        *
        *The above copyright notice and this permission notice shall be included in all
        * copies or substantial portions of the Software.
        *
        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        * SOFTWARE.*/

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;

import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.openpgp.*;
import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.spongycastle.openpgp.operator.PGPDataEncryptorBuilder;
import org.spongycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.spongycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;

import uk.co.platosys.minigma.Key;
import uk.co.platosys.minigma.Lock;
import uk.co.platosys.minigma.Minigma;
import uk.co.platosys.minigma.exceptions.BadPassphraseException;
import uk.co.platosys.minigma.exceptions.DecryptionException;
import uk.co.platosys.minigma.exceptions.Exceptions;
import uk.co.platosys.minigma.exceptions.MinigmaException;
import uk.co.platosys.minigma.exceptions.MinigmaOtherException;
import uk.co.platosys.minigma.exceptions.NoDecryptionKeyException;
import uk.co.platosys.minigma.utils.Kidney;
import uk.co.platosys.minigma.utils.MinigmaUtils;


/**
 * this  class holds the static decrypt and encrypt methods
 *
 * @author edward
 */
public  class CryptoEngine {
    private static String TAG ="CryptoEngine";

    /**
     *  Decrypts an InputStream to a byte array
     *
     * @param inputStream
     * @param key
     * @param passphrase
     * @return
     * @throws Exception
     */

    public static byte[] decrypt(InputStream inputStream, Key key, char[] passphrase)
            throws MinigmaOtherException,
            BadPassphraseException
            {
        InputStream decoderStream;
        PGPObjectFactory pgpObjectFactory=null;
        PGPEncryptedDataList pgpEncryptedDataList = null;
        try {
            decoderStream = PGPUtil.getDecoderStream(inputStream);
            pgpObjectFactory = new PGPObjectFactory(decoderStream, new JcaKeyFingerprintCalculator());
            boolean moreObjects=true;
            while (moreObjects) {
                Object object = pgpObjectFactory.nextObject();
                if (object == null) {
                    moreObjects = false;
                }
                if (object instanceof PGPEncryptedDataList) {
                    pgpEncryptedDataList = (PGPEncryptedDataList) object;
                    PGPCompressedData compressedData = decrypt(pgpEncryptedDataList, key, passphrase);
                    return decompress(compressedData);
                } else {
                    System.out.println(object.getClass().getName());
                }
            }
            throw new MinigmaException("couldn't find encrypted data list");
        }catch(BadPassphraseException bpe){
            throw bpe;
        } catch(Exception e){
            Exceptions.dump(e);
            throw new MinigmaOtherException("error reading encrypted data list", e);
        }
    }

    /**
     * An encryptedDataList will contain one or more blocks of encrypted data, usually the same literal data encrypted
     * to one or more public keys. Typically, the provided Key will only be able to unlock one of them.
     * @param pgpEncryptedDataList
     * @param key
     * @param passphrase
     * @return
     * @throws MinigmaException
     * @throws DecryptionException
     */
    private static PGPCompressedData decrypt(PGPEncryptedDataList pgpEncryptedDataList, Key key, char[] passphrase) throws MinigmaOtherException, BadPassphraseException, DecryptionException {
        PGPPrivateKey privateKey = null;
        PGPPublicKeyEncryptedData pgpPublicKeyEncryptedData = null;
        try {
            Iterator<PGPPublicKeyEncryptedData> it = pgpEncryptedDataList.getEncryptedDataObjects();
            JcePBESecretKeyDecryptorBuilder keyDecryptorBuilder = new JcePBESecretKeyDecryptorBuilder();
            keyDecryptorBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME);
            int size = pgpEncryptedDataList.size();
            int count = 0;
            while (it.hasNext() && privateKey == null) {
                pgpPublicKeyEncryptedData = it.next();
                count++;
                //System.out.println();
                long keyID = pgpPublicKeyEncryptedData.getKeyID();
                //System.out.println("EncryptedDataBlock was encrypted with keyID "+Kidney.toString(keyID));
                try {
                    PGPSecretKey secretKey = key.getDecryptionKey(keyID);
                    if (secretKey.getKeyID() == keyID) {
                        try {
                            privateKey = key.getDecryptionKey(keyID).extractPrivateKey(keyDecryptorBuilder.build(passphrase));
                            //System.out.println("Key match for "+Kidney.toString(keyID));
                        } catch (PGPException pgpException) {
                            throw new BadPassphraseException("bad passphrase", pgpException);

                        }
                    }
                } catch (BadPassphraseException bpe) {
                    throw bpe;
                } catch
                (NoDecryptionKeyException ndke) {
                    //System.out.println("no decryption key available for keyID "+Kidney.toString(keyID));
                    //we don't need to worry about this exception here.
                } catch (Exception x) {
                    System.out.println("oops exception in decrypt while loop");
                    Exceptions.dump(x);
                    throw new MinigmaException("CryptoEngine: getEncryptedDataObjects - unexpected exception", x);
                }
            }
            if (privateKey == null) {
                //System.out.println("Done "+ count + "keys of "+size+" altogether, still no private key");
                throw new DecryptionException("CryptoEngine: decryption key doesn't fit any of the locks");
            }
        }catch(BadPassphraseException bpe){
            throw bpe;
        }catch (DecryptionException dx) { //don't think this is ever thrown here
            Exceptions.dump(dx);
            throw dx;
        }catch (Exception e) {
            Exceptions.dump(e);
            throw new MinigmaOtherException("A problem arose during decryption", e);
        }
        //so we now have an encrypted data object and a key that fits it...
        try {
            PublicKeyDataDecryptorFactory dataDecryptorFactory = new BcPublicKeyDataDecryptorFactory(privateKey);
            InputStream decryptedStream = pgpPublicKeyEncryptedData.getDataStream(dataDecryptorFactory);
            JcaPGPObjectFactory compressedFactory = new JcaPGPObjectFactory(decryptedStream);
            return (PGPCompressedData) compressedFactory.nextObject();

        } catch (Exception e) {
            Exceptions.dump(e);
            throw new MinigmaOtherException("Minigma-unLock() 3: error reading encrypted data stream", e);
        }
    }

    private static byte[] decompress (PGPCompressedData clearCompressedData) throws  MinigmaOtherException{
        PGPLiteralData literalData=null;
        try {
            InputStream inputStream = clearCompressedData.getDataStream();
            JcaPGPObjectFactory decompressedFactory = new JcaPGPObjectFactory(inputStream);
            boolean moreObjects=true;
            while ((literalData==null)&&(moreObjects)) {
                Object decompressedObject = decompressedFactory.nextObject();
                if (decompressedObject==null){moreObjects=false;}
                if (decompressedObject instanceof PGPLiteralData) {
                    literalData = (PGPLiteralData) decompressedObject;
                }
            }
            return MinigmaUtils.readStream(literalData.getDataStream());
        }catch(Exception e){
            Exceptions.dump(e);
            throw new MinigmaOtherException( "Minigma-unLock() 4: error getting decompressed object", e );
        }
    }

    /**
     * Returns a byte array of encrypted data. The resultant binary data must be base64 encoded
     * for transport by text systems such as xml.
     * @param compressedData
     * @param lock
     * @return
     * @throws MinigmaException
     */
    @SuppressWarnings("resource")
    public static byte[] encrypt (byte[] compressedData, Lock lock) throws MinigmaException{
        Minigma.initialiseProvider();
        PGPEncryptedDataGenerator encryptedDataGenerator=configureGenerator(Algorithms.SYMMETRIC_ALGORITHM,lock);
        ByteArrayOutputStream encryptedByteStream = new ByteArrayOutputStream();
        OutputStream outputStream;

        try {
            outputStream = encryptedDataGenerator.open(encryptedByteStream, compressedData.length);
        }catch(PGPException pgpe) {
            Exceptions.dump(pgpe);
            throw new MinigmaException("Error generating cypher: have you installed the unlimited strength policy files?", pgpe);
        }catch(Exception e){
            Exceptions.dump(e);
            throw new MinigmaException("Error generating cypher: refer to stack trace for details", e);

        }try{
            outputStream.write(compressedData);
            outputStream.flush();
            outputStream.close();
            byte[] encryptedBytes = encryptedByteStream.toByteArray();
            encryptedDataGenerator.close();
            return encryptedBytes;
        }catch(Exception e){
            Exceptions.dump(e);
            throw new MinigmaException("Cryptoengine-encrypt: ", e);
        }
    }

    private  static PGPEncryptedDataGenerator configureGenerator(int algorithm, Lock lock) throws MinigmaException {
        PGPEncryptedDataGenerator encryptedDataGenerator;

        try{
            JcePGPDataEncryptorBuilder pgpDataEncryptorBuilder = new JcePGPDataEncryptorBuilder(algorithm);
            pgpDataEncryptorBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME);
            encryptedDataGenerator = new PGPEncryptedDataGenerator(pgpDataEncryptorBuilder);
            Iterator<PGPPublicKeyRing> it = lock.getPGPPublicKeyRingIterator();
            if (!it.hasNext()){
                throw new MinigmaException("Empty Lock: "+lock.toString());
            }
            while (it.hasNext()){
                PGPPublicKeyRing keyRing = it.next();
                Iterator<PGPPublicKey> publicKeyIterator = keyRing.getPublicKeys();
                while(publicKeyIterator.hasNext()){
                    PGPPublicKey pgpPublicKey = publicKeyIterator.next();
                    if(pgpPublicKey.isEncryptionKey()){
                        PGPKeyEncryptionMethodGenerator methodGenerator = new JcePublicKeyKeyEncryptionMethodGenerator(pgpPublicKey);
                        encryptedDataGenerator.addMethod(methodGenerator);
                        System.out.println("added encryption method for keyID "+ Kidney.toString(pgpPublicKey.getKeyID()));
                    }
                }
            }
            return encryptedDataGenerator;
        }catch(Exception e){
            throw new MinigmaException("Minigma-encrypt: error configuring generator",e);
        }
    }
}

<file_sep>/app/src/main/java/uk/co/platosys/minigma/utils/FileTools.java
/*
 *
    * (c) copyright 2018 Platosys
        * MIT Licence
        * Permission is hereby granted, free of charge, to any person obtaining a copy
        * of this software and associated documentation files (the "Software"), to deal
        * in the Software without restriction, including without limitation the rights
        * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        * copies of the Software, and to permit persons to whom the Software is
        * furnished to do so, subject to the following conditions:
        *
         *The above copyright notice and this permission notice shall be included in all
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
 */

package uk.co.platosys.minigma.utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
 *
 * @author edward
 */
public class FileTools {
    /**
     * Recursively deletes files and directories and their contents (equivalent to rm -r )
     * (assumes no permission issues, doesn't trap them yet);
     */
    public static void delete(File file){
        if(!file.isDirectory()){
            file.delete();
        }else{
            File[] files = file.listFiles();
            for (int i=0; i<files.length; i++){
                delete(files[i]);
            }
            file.delete();
        }
    }
    /**this removes spaces and any funny characters from the supplied string, but keeps dots.
     *
     * handy to process strings to make them more useful as cross-platform filenames
     *
     * @param string
     * @return
     */
    public static String removeFunnyCharacters(String string){
        StringBuffer buffer = new StringBuffer();
        char dot = '.';
        for (int i=0; i<string.length(); i++){
            char x = string.charAt(i);
            if (Character.isLetterOrDigit(x)){
                buffer.append(x);
            }
            if (x==dot){buffer.append(x);}
        }
        return new String(buffer);
    }
    /**
     * simple file copy utility
     * @param fromFile
     * @param toFile
     * @throws IOException
     */
    public static void copy(File fromFile, File toFile)
            throws IOException {


        if (!fromFile.exists())
            throw new IOException("FileCopy: " + "no such source file: "
                    + fromFile.getAbsolutePath());
        if (!fromFile.isFile())
            throw new IOException("FileCopy: " + "can't copy directory: "
                    + fromFile.getAbsolutePath());
        if (!fromFile.canRead())
            throw new IOException("FileCopy: " + "source file is unreadable: "
                    + fromFile.getAbsolutePath());

        if (toFile.isDirectory())
            toFile = new File(toFile, fromFile.getName());

        if (toFile.exists()) {
            if (!toFile.canWrite())
                throw new IOException("FileCopy: "
                        + "destination file is unwriteable: " + toFile.getAbsolutePath());
            System.out.print("Overwrite existing file " + toFile.getName()
                    + "? (Y/N): ");
            System.out.flush();
            BufferedReader in = new BufferedReader(new InputStreamReader(
                    System.in));
            String response = in.readLine();
            if (!response.equals("Y") && !response.equals("y"))
                throw new IOException("FileCopy: "
                        + "existing file was not overwritten.");
        } else {
            String parent = toFile.getParent();
            if (parent == null)
                parent = System.getProperty("user.dir");
            File dir = new File(parent);
            if (!dir.exists())
                throw new IOException("FileCopy: "
                        + "destination directory doesn't exist: " + parent);
            if (dir.isFile())
                throw new IOException("FileCopy: "
                        + "destination is not a directory: " + parent);
            if (!dir.canWrite())
                throw new IOException("FileCopy: "
                        + "destination directory is unwriteable: " + parent);
        }

        FileInputStream from = null;
        FileOutputStream to = null;
        try {
            from = new FileInputStream(fromFile);
            to = new FileOutputStream(toFile);
            byte[] buffer = new byte[4096];
            int bytesRead;

            while ((bytesRead = from.read(buffer)) != -1)
                to.write(buffer, 0, bytesRead); // write
        } finally {
            if (from != null)
                try {
                    from.close();
                } catch (IOException e) {

                }
            if (to != null)
                try {
                    to.close();
                } catch (IOException e) {

                }
        }
    }
}

<file_sep>/app/src/main/java/uk/co/platosys/minigma/votes/Officer.java
package uk.co.platosys.minigma.votes;

import uk.co.platosys.minigma.Key;

/**
 *  Objects implementing the Officer interface represent the Returning Officer of a poll. Their Key is used
 *  to unwrap the Ballots sent to them by Voters.
 */

public interface Officer extends Voter {
    Key getKey();
}
