diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/BitInputStream.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/BitInputStream.java new file mode 100644 index 000000000..b6ec7234e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/BitInputStream.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) Andreas Jakl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.experimental; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +/** + * The BitInputStream allows reading individual bits from a + * general Java InputStream. + * Like the various Stream-classes from Java, the BitInputStream + * has to be created based on another Input stream. It provides + * a function to read the next bit from the sream, as well as to read multiple + * bits at once and write the resulting data into an integer value. + *

+ * source: http://developer.nokia.com/Community/Wiki/Bit_Input/Output_Stream_utility_classes_for_efficient_data_transfer + */ +public class BitInputStream { + /** + * The Java InputStream this class is working on. + */ + private InputStream iIs; + + /** + * The buffer containing the currently processed + * byte of the input stream. + */ + private int iBuffer; + + /** + * Next bit of the current byte value that the user will + * get. If it's 8, the next bit will be read from the + * next byte of the InputStream. + */ + private int iNextBit = 8; + + /** + * Create a new bit input stream based on an existing Java InputStream. + * + * @param aIs the input stream this class should read the bits from. + */ + public BitInputStream(InputStream aIs) { + iIs = aIs; + } + + /** + * Read a specified number of bits and return them combined as + * an integer value. The bits are written to the integer + * starting at the highest bit ( << aNumberOfBits ), going down + * to the lowest bit ( << 0 ) + * + * @param aNumberOfBits defines how many bits to read from the stream. + * @return integer value containing the bits read from the stream. + * @throws IOException + */ + synchronized public int readBits(final int aNumberOfBits) + throws IOException { + int value = 0; + for (int i = aNumberOfBits - 1; i >= 0; i--) { + value |= (readBit() << i); + } + return value; + } + + synchronized public int available() { + try { + return (8 - iNextBit) + iIs.available() * 8; // bytestream to bitstream available + } catch (Exception e) { + return 0; + } + } + + /** + * Read the next bit from the stream. + * + * @return 0 if the bit is 0, 1 if the bit is 1. + * @throws IOException + */ + synchronized public int readBit() throws IOException { + if (iIs == null) + throw new IOException("Already closed"); + + if (iNextBit == 8) { + iBuffer = iIs.read(); + + if (iBuffer == -1) + throw new EOFException(); + + iNextBit = 0; + } + + int bit = iBuffer & (1 << iNextBit); + iNextBit++; + + bit = (bit == 0) ? 0 : 1; + + return bit; + } + + /** + * Close the underlying input stream. + * + * @throws IOException + */ + public void close() throws IOException { + iIs.close(); + iIs = null; + } +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/SentenceConfirm.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/SentenceConfirm.java new file mode 100644 index 000000000..ead70b8f6 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/SentenceConfirm.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * Copyright (C) 2014 Jake McGinty (Open Whisper Systems) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.experimental; + +import android.content.Context; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +/** + * From https://github.com/mcginty/TextSecure/tree/mnemonic-poem + */ +public class SentenceConfirm { + Context context; + List n, vi, vt, adj, adv, p, art; + + public SentenceConfirm(Context context) { + this.context = context; + try { + n = readFile(R.raw.fp_sentence_nouns); + vi = readFile(R.raw.fp_sentence_verbs_i); + vt = readFile(R.raw.fp_sentence_verbs_t); + adj = readFile(R.raw.fp_sentence_adjectives); + adv = readFile(R.raw.fp_sentence_adverbs); + p = readFile(R.raw.fp_sentence_prepositions); + art = readFile(R.raw.fp_sentence_articles); + } catch (IOException e) { + Log.e(Constants.TAG, "Reading sentence files failed", e); + } + } + + List readFile(int resId) throws IOException { + if (context.getApplicationContext() == null) { + throw new AssertionError("app context can't be null"); + } + + BufferedReader in = new BufferedReader(new InputStreamReader( + context.getApplicationContext() + .getResources() + .openRawResource(resId))); + List words = new ArrayList<>(); + String word = in.readLine(); + while (word != null) { + words.add(word); + word = in.readLine(); + } + in.close(); + return words; + } + + public String fromBytes(final byte[] bytes, int desiredBytes) throws IOException { + BitInputStream bin = new BitInputStream(new ByteArrayInputStream(bytes)); + EntropyString fingerprint = new EntropyString(); + + while (fingerprint.getBits() < (desiredBytes * 8)) { + if (!fingerprint.isEmpty()) { + fingerprint.append("\n\n"); + } + try { + fingerprint.append(getSentence(bin)); + } catch (IOException e) { + Log.e(Constants.TAG, "IOException when creating the sentence"); + throw e; + } + } + return fingerprint.toString(); + } + + /** + * Grab a word for a list of them using the necessary bits to choose from a BitInputStream + * + * @param words the list of words to select from + * @param bin the bit input stream to encode from + * @return A Pair of the word and the number of bits consumed from the stream + */ + private EntropyString getWord(List words, BitInputStream bin) throws IOException { + final int neededBits = log(words.size(), 2); + Log.d(Constants.TAG, "need " + neededBits + " bits of entropy"); + int bits = bin.readBits(neededBits); + Log.d(Constants.TAG, "got word " + words.get(bits) + " with " + neededBits + " bits of entropy"); + return new EntropyString(words.get(bits), neededBits); + } + + private EntropyString getNounPhrase(BitInputStream bits) throws IOException { + final EntropyString phrase = new EntropyString(); + phrase.append(getWord(art, bits)).append(" "); + if (bits.readBit() != 0) { + phrase.append(getWord(adj, bits)).append(" "); + } + phrase.incBits(); + + phrase.append(getWord(n, bits)); + Log.d(Constants.TAG, "got phrase " + phrase + " with " + phrase.getBits() + " bits of entropy"); + return phrase; + } + + EntropyString getSentence(BitInputStream bits) throws IOException { + final EntropyString sentence = new EntropyString(); + sentence.append(getNounPhrase(bits)); // Subject + if (bits.readBit() != 0) { + sentence.append(" ").append(getWord(vt, bits)); // Transitive verb + sentence.append(" ").append(getNounPhrase(bits)); // Object of transitive verb + } else { + sentence.append(" ").append(getWord(vi, bits)); // Intransitive verb + } + sentence.incBits(); + + if (bits.readBit() != 0) { + sentence.append(" ").append(getWord(adv, bits)); // Adverb + } + + sentence.incBits(); + if (bits.readBit() != 0) { + sentence.append(" ").append(getWord(p, bits)); // Preposition + sentence.append(" ").append(getNounPhrase(bits)); // Object of preposition + } + sentence.incBits(); + Log.d(Constants.TAG, "got sentence " + sentence + " with " + sentence.getBits() + " bits of entropy"); + + // uppercase first character, end with dot (without increasing the bits) + sentence.getBuilder().replace(0, 1, + Character.toString(Character.toUpperCase(sentence.getBuilder().charAt(0)))); + sentence.getBuilder().append("."); + + return sentence; + } + + public static class EntropyString { + private StringBuilder builder; + private int bits; + + public EntropyString(String phrase, int bits) { + this.builder = new StringBuilder(phrase); + this.bits = bits; + } + + public EntropyString() { + this("", 0); + } + + public StringBuilder getBuilder() { + return builder; + } + + public boolean isEmpty() { + return builder.length() == 0; + } + + public EntropyString append(EntropyString phrase) { + builder.append(phrase); + bits += phrase.getBits(); + return this; + } + + public EntropyString append(String string) { + builder.append(string); + return this; + } + + public int getBits() { + return bits; + } + + public void setBits(int bits) { + this.bits = bits; + } + + public void incBits() { + bits += 1; + } + + @Override + public String toString() { + return builder.toString(); + } + } + + private static int log(int x, int base) { + return (int) (Math.log(x) / Math.log(base)); + } + +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/WordConfirm.java similarity index 95% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/WordConfirm.java index 43ccac24f..daf63ea9e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/WordConfirm.java @@ -15,12 +15,13 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.ui.util; +package org.sufficientlysecure.keychain.experimental; import android.content.Context; import org.spongycastle.util.Arrays; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.Log; import java.io.BufferedReader; @@ -29,7 +30,7 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.BitSet; -public class ExperimentalWordConfirm { +public class WordConfirm { public static String getWords(Context context, byte[] fingerprintBlob) { ArrayList words = new ArrayList<>(); @@ -37,7 +38,7 @@ public class ExperimentalWordConfirm { BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader( - context.getAssets().open("word_confirm_list.txt"), + context.getResources().openRawResource(R.raw.fp_word_list), "UTF-8" )); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java index 552fa34c0..d9bc08268 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java @@ -33,12 +33,14 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.experimental.SentenceConfirm; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.util.ExperimentalWordConfirm; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import java.io.IOException; + public class CertifyFingerprintFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks { @@ -180,9 +182,16 @@ public class CertifyFingerprintFragment extends LoaderFragment implements } private void displayWordConfirm(byte[] fingerprintBlob) { - String fingerprint = ExperimentalWordConfirm.getWords(getActivity(), fingerprintBlob); +// String fingerprint = ExperimentalWordConfirm.getWords(getActivity(), fingerprintBlob); - mFingerprint.setTextSize(24); + String fingerprint; + try { + fingerprint = new SentenceConfirm(getActivity()).fromBytes(fingerprintBlob, 16); + } catch (IOException ioe) { + fingerprint = "-"; + } + + mFingerprint.setTextSize(18); mFingerprint.setTypeface(Typeface.DEFAULT, Typeface.BOLD); mFingerprint.setText(fingerprint); } diff --git a/OpenKeychain/src/main/res/raw/fp_sentence_adjectives b/OpenKeychain/src/main/res/raw/fp_sentence_adjectives new file mode 100644 index 000000000..cf8fa4674 --- /dev/null +++ b/OpenKeychain/src/main/res/raw/fp_sentence_adjectives @@ -0,0 +1,128 @@ +able +angry +bad +bent +bitter +black +blue +boiling +bright +broken +brown +certain +cheap +clean +clear +cold +common +complex +cruel +dark +dead +dear +deep +dirty +dry +early +elastic +equal +false +fat +feeble +female +fertile +first +fixed +flat +foolish +free +full +future +general +good +great +green +grey +hanging +happy +hard +healthy +high +hollow +kind +last +late +lazy +left +like +living +long +loose +loud +low +male +married +medical +mixed +narrow +natural +new +normal +old +open +past +poor +present +pretty +private +public +quick +quiet +ready +rare +red +regular +right +rough +round +sad +safe +same +second +secret +serious +sharp +short +shut +sick +simple +slow +small +smooth +soft +solid +sour +special +sticky +stiff +strange +strong +sudden +sweet +tall +thick +thin +tight +tired +true +unknown +violent +waiting +warm +wet +white +wide +wise +wrong +yellow +young diff --git a/OpenKeychain/src/main/res/raw/fp_sentence_adverbs b/OpenKeychain/src/main/res/raw/fp_sentence_adverbs new file mode 100644 index 000000000..1d6002387 --- /dev/null +++ b/OpenKeychain/src/main/res/raw/fp_sentence_adverbs @@ -0,0 +1,64 @@ +ably +angrily +badly +bitterly +brightly +brokenly +cheaply +clearly +coldly +commonly +cruelly +darkly +dearly +deeply +drily +equally +falsely +feebly +fixedly +flatly +freely +fully +greatly +happily +hardly +kindly +lately +lazily +loosely +loudly +narrowly +newly +normally +openly +poorly +prettily +publicly +quickly +quietly +readily +rarely +roughly +sadly +safely +secretly +sharply +simply +slowly +smoothly +softly +solidly +sourly +stiffly +strongly +suddenly +sweetly +thickly +thinly +tightly +tiredly +truly +warmly +widely +wisely diff --git a/OpenKeychain/src/main/res/raw/fp_sentence_articles b/OpenKeychain/src/main/res/raw/fp_sentence_articles new file mode 100644 index 000000000..0604b3d07 --- /dev/null +++ b/OpenKeychain/src/main/res/raw/fp_sentence_articles @@ -0,0 +1,8 @@ +her +his +my +our +that +the +this +your diff --git a/OpenKeychain/src/main/res/raw/fp_sentence_nouns b/OpenKeychain/src/main/res/raw/fp_sentence_nouns new file mode 100644 index 000000000..ed4099c31 --- /dev/null +++ b/OpenKeychain/src/main/res/raw/fp_sentence_nouns @@ -0,0 +1,512 @@ +account +air +amount +angle +animal +answer +ant +apple +arch +arm +army +attack +attempt +baby +back +bag +ball +band +base +basin +basket +bath +bed +bee +belief +bell +berry +bird +birth +bite +blade +blood +blow +board +boat +body +bone +book +boot +bottle +box +boy +brain +brake +branch +bread +breath +brick +bridge +brother +brush +bucket +bulb +burn +butter +button +cake +camera +canvas +car +card +cat +cause +chain +chalk +chance +change +cheese +chest +chin +church +circle +clock +cloth +cloud +coal +coat +collar +colour +comb +comfort +company +control +cook +copper +copy +cord +cork +cotton +cough +country +cover +cow +crack +credit +crime +crush +cry +cup +current +curtain +curve +cushion +damage +danger +day +debt +degree +design +desire +detail +disease +disgust +dog +door +doubt +drain +drawer +dress +drink +drop +dust +ear +earth +edge +effect +egg +end +engine +error +event +example +expert +eye +face +fact +fall +family +farm +father +fear +feather +feeling +fiction +field +fight +finger +fire +fish +flag +flame +flight +floor +flower +fly +fold +food +foot +force +fork +form +fowl +frame +friend +front +fruit +garden +girl +glass +glove +goat +gold +grain +grass +grip +group +growth +guide +gun +hair +hammer +hand +harbour +harmony +hat +hate +head +heart +heat +history +hole +hook +hope +horn +horse +hour +house +humour +ice +idea +impulse +ink +insect +iron +island +jelly +jewel +join +journey +judge +jump +kettle +key +kick +kiss +knee +knife +knot +land +laugh +law +lead +leaf +leather +leg +letter +level +library +lift +light +limit +line +linen +lip +liquid +list +lock +look +loss +love +machine +man +manager +map +mark +market +mass +match +meal +measure +meat +meeting +memory +metal +middle +milk +mind +mine +minute +mist +money +monkey +month +moon +morning +mother +motion +mouth +move +muscle +music +nail +name +nation +neck +need +needle +nerve +net +news +night +noise +nose +note +number +nut +offer +office +oil +opinion +orange +order +oven +owner +page +pain +paint +paper +parcel +part +paste +payment +peace +pen +pencil +person +picture +pig +pin +pipe +place +plane +plant +plate +play +plough +pocket +point +poison +polish +porter +pot +potato +powder +power +price +print +prison +process +produce +profit +prose +protest +pull +pump +purpose +push +quality +rail +rain +range +rat +rate +ray +reason +receipt +record +regret +request +respect +rest +reward +rhythm +rice +ring +river +road +rod +roll +roof +room +root +rub +rule +run +sail +salt +sand +scale +school +science +screw +sea +seat +seed +self +sense +servant +sex +shade +shake +shame +sheep +shelf +ship +shirt +shock +shoe +side +sign +silk +silver +sister +size +skin +skirt +sky +sleep +slip +slope +smash +smell +smile +smoke +snake +sneeze +snow +soap +society +sock +son +song +sort +sound +soup +space +spade +sponge +spoon +spring +square +stage +stamp +star +start +station +steam +steel +stem +step +stick +stitch +stomach +stone +stop +store +story +street +stretch +sugar +summer +sun +support +swim +system +table +tail +talk +taste +tax +test +theory +thing +thought +thread +throat +thumb +thunder +ticket +time +tin +toe +tongue +tooth +top +touch +town +trade +train +tray +tree +trick +trouble +turn +twist +unit +value +verse +vessel +view +voice +walk +wall +war +wash +waste +watch +water +wave +wax +way +weather +week +weight +wheel +whip +whistle +wind +window +wine +wing +winter +wire +woman +wood +wool +word +work +worm +wound +writing +year diff --git a/OpenKeychain/src/main/res/raw/fp_sentence_prepositions b/OpenKeychain/src/main/res/raw/fp_sentence_prepositions new file mode 100644 index 000000000..fb8206636 --- /dev/null +++ b/OpenKeychain/src/main/res/raw/fp_sentence_prepositions @@ -0,0 +1,32 @@ +above +across +after +against +along +among +around +at +before +behind +below +beneath +beside +between +beyond +by +from +in +inside +into +near +on +outside +over +past +round +through +to +towards +under +upon +with diff --git a/OpenKeychain/src/main/res/raw/fp_sentence_verbs_i b/OpenKeychain/src/main/res/raw/fp_sentence_verbs_i new file mode 100644 index 000000000..57602bf17 --- /dev/null +++ b/OpenKeychain/src/main/res/raw/fp_sentence_verbs_i @@ -0,0 +1,128 @@ +agrees +allows +answers +arrives +asks +is +becomes +begins +believes +brings +burns +buys +calls +changes +chooses +cleans +closes +comes +compares +continues +cooks +costs +counts +cries +cuts +dances +decides +describes +destroys +dies +does +drinks +drives +eats +ends +explains +falls +feels +fights +fills +finds +finishes +forgets +forgives +gets +gives +goes +grows +hates +has +hears +helps +hides +holds +hurts +improves +jumps +keeps +kills +knows +laughs +learns +leaves +lets +lies +listens +lives +looks +loses +loves +makes +meets +moves +needs +occurs +offers +opens +pays +plays +prefers +prepares +presses +promises +pulls +pushes +puts +reads +receives +remembers +repeats +rests +returns +runs +sees +sells +sends +shouts +shows +sings +sits +sleeps +smiles +speaks +starts +stays +stops +studies +suggests +supports +takes +talks +teaches +tells +thinks +throws +touches +travels +treats +tries +turns +uses +visits +walks +wants +washes +wins +works +writes diff --git a/OpenKeychain/src/main/res/raw/fp_sentence_verbs_t b/OpenKeychain/src/main/res/raw/fp_sentence_verbs_t new file mode 100644 index 000000000..36078bc78 --- /dev/null +++ b/OpenKeychain/src/main/res/raw/fp_sentence_verbs_t @@ -0,0 +1,128 @@ +agrees with +allows +answers +arrives at +asks +is +becomes +begins +believes +brings +burns +buys +calls +changes +chooses +cleans +closes +comes to +compares +continues +cooks +costs +counts +cries for +cuts +dances with +decides on +describes +destroys +dies for +does +drinks +drives +eats +ends +explains +falls on +feels +fights +fills +finds +finishes +forgets +forgives +gets +gives +goes to +grows +hates +has +hears +helps +hides +holds +hurts +improves +jumps over +keeps +kills +knows +laughs at +learns +leaves +lets +lies to +listens to +lives with +looks at +loses +loves +makes +meets +moves +needs +occurs to +offers +opens +pays +plays +prefers +prepares +presses +promises +pulls +pushes +puts +reads +receives +remembers +repeats +rests by +returns +runs to +sees +sells +sends +shouts at +shows +sings to +sits by +sleeps by +smiles at +speaks to +starts +stays with +stops +studies +suggests +supports +takes +talks to +teaches +tells +thinks of +throws +touches +travels to +treats +tries +turns +uses +visits +walks to +wants +washes +wins +works for +writes to diff --git a/OpenKeychain/src/main/assets/word_confirm_list.txt b/OpenKeychain/src/main/res/raw/fp_word_list similarity index 100% rename from OpenKeychain/src/main/assets/word_confirm_list.txt rename to OpenKeychain/src/main/res/raw/fp_word_list diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index c83b598ed..2870f4d5c 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -115,7 +115,7 @@ "Update all keys" "Extended information" "Confirm via fingerprint" - "Confirm via words" + "Confirm via phrases" "Share Log" "Add" @@ -195,8 +195,8 @@ "Warning" "These features are not yet finished or results of user experience/security research. Thus, don't rely on their security and please don't report issues you encounter!" - "Word Confirm" - "Confirm keys with words instead of hexadecimal fingerprints" + "Phrase Confirmation" + "Confirm keys with phrases instead of hexadecimal fingerprints" "Linked Identities" "Link keys to Twitter, GitHub, websites or DNS (similar to keybase.io but decentralized)" "Keybase.io Proofs" @@ -1447,8 +1447,8 @@ "Only validated self-certificates and validated certificates created with your keys are displayed here." "Identities for " "The keys you are importing contain “identities”: names and email addresses. Select exactly those for confirmation which match what you expected." - "Compare the displayed fingerprint, character by character, with the one displayed on your partners device." - "Compare the displayed fingerprint, word by word, with the one displayed on your partners device." + "Compare the fingerprint, character by character, with the one displayed on your partners device." + "Compare the phrases with the ones displayed on your partners device." "Do the fingerprints match?" "Revocation Reason" "Type"