Skip to content

Instantly share code, notes, and snippets.

@elsewhat
Last active December 11, 2015 01:48
Show Gist options
  • Save elsewhat/4525762 to your computer and use it in GitHub Desktop.
Save elsewhat/4525762 to your computer and use it in GitHub Desktop.
Ingress Pontifax code testing Output can be found here: https://docs.google.com/document/d/1Z2Baw94tlcoRODV_g89nOy2FgI6Dvx-vy6eV8Wpwpus/edit
// SolitaireCipher.java
// Copyright (C) 1999 Jeff Gold
// 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 2 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 (see License.txt); if not, see
// http://www.gnu.org
// or write to the Free Software Foundation, Inc., 59 Temple Place,
// Suite 330, Boston, MA 02111-1307 USA
// NOTE: the Solitaire encryption algorithm is strong cryptography.
// That means the security it affords is based on the secrecy of the
// key rather than secrecy of the algorithm itself. That also means
// that this program and programs derived from it may be treated as
// a munition for the purpose of export regulation in the United States
// and other countries. You are encouraged to seek competent legal
// counsel before distributing copies of this program.
//
// This package contains a Java implementation of the Solitaire
// encryption algorithm, as designed by Bruce Schneier and described at:
// http://www.counterpane.com/solitaire.html
// as well as in Neil Stephenson's novel Cryptonomicon.
// Solitaire is a cryptographically strong stream cipher that can
// be implemented using a deck of playing cards. This implementation
// is not designed for high performance -- this is a Java program, after
// all. Instead it is intended to be clean, portable, and easy to
// understand.
import java.lang.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import java.io.Serializable;
public final class
SolitaireCipher
implements Serializable, Cloneable
{
/**
* All ingress code logic handled inside main methode
* Rest is from schneier's pontifax code
* http://www.schneier.com/solitaire.html
*/
public static void main(String args[]) {
String ingressCipher = "YPEJOZLPHRZMKAPLVUMAIHXDSFOXMHPPGHCJKEMQAVSTJ";
System.out.println("Start - Attempting various variants of the incomplete keyphrase and checking if deck after keying 3S 7S 10S ...");
String keyPhraseLine1="DEALTWITH";
String[] keyPhraseLine2Alt={
"BYDISCRET",
"BYDISCRETE",
"BYDISCREET",
"BYDISCREDITS",
"BYDISCRETELY",
"BYDISCREETER",
"BYDISCRETION",
"BYDISCREETLY",
"BYDISCREPANT",
"BYDISCREDITED",
"BYDISCREETEST",
"BYDISCREPANCY",
"BYDISCRETIONS"
};
String[] keyPhraseLine3Alt={
"MACHINATING",
"MACHES",
"MACHOS",
"MACHETE",
"MACHREE",
"MACHZOR",
"MACHINE",
"MACHETES",
"MACHINED",
"MACHINES",
"MACHISMO",
"MACHOISM",
"MACHREES",
"MACHZORS",
"MACHINATE",
"MACHINERY",
"MACHINING",
"MACHINIST",
"MACHISMOS",
"MACHOISMS",
"MACHZORIM",
"MACHINABLE",
"MACHINATED",
"MACHINATES",
"MACHINATOR",
"MACHINISTS",
"MACHINATING",
"MACHINATION",
"MACHINATORS",
"MACHINEABLE",
"MACHINELIKE",
"MACHINERIES"
};
ArrayList potentialCorrectKeyphrases= new ArrayList();
int combinations=0;
for (int line2 = 0; line2 < keyPhraseLine2Alt.length; line2++) {
for (int line3 = 0; line3 < keyPhraseLine3Alt.length; line3++) {
combinations++;
String keyPhrase = keyPhraseLine1+keyPhraseLine2Alt[line2]+ keyPhraseLine3Alt[line3];
//System.out.println(keyPhrase);
SolitaireCipher sc = new SolitaireCipher(keyPhrase);
Deck deckAfterKeying = sc.keyDeck;
byte topCard = deckAfterKeying.peekTop(0);
byte secondCard =deckAfterKeying.peekTop(1);
byte thirdCard = deckAfterKeying.peekTop(2);
//System.out.println(keyPhrase + " top card "+Deck.cardString(topCard) + " second card "+Deck.cardString(secondCard));
if(Deck.isSpade(topCard) && Deck.isThree(topCard)
&& Deck.isSpade(secondCard) && Deck.isSeven(secondCard)
&& Deck.isSpade(thirdCard) && Deck.isTen(thirdCard)){
potentialCorrectKeyphrases.add(keyPhrase);
}
//not needed, as we only do it for the candidates
String outputtext = sc.decrypt(ingressCipher);
}
}
System.out.println("Tried "+ combinations + " for the key phrasse");
if(potentialCorrectKeyphrases.size()==0) {
System.out.println("No suitable candidates found (ie. last card three of spades");
}else {
for (Iterator iterator = potentialCorrectKeyphrases.iterator(); iterator
.hasNext();) {
String keyPhrase = (String) iterator.next();
System.out.println("Suitable candidates found " +keyPhrase);
SolitaireCipher sc = new SolitaireCipher(keyPhrase);
Deck deckAfterKeying = (Deck) sc.keyDeck.clone();
System.out.println("Deck after keying (should match photo)\n" + deckAfterKeying);
String outputtext = sc.decrypt(ingressCipher);
System.out.println("Decrypted value\n" + outputtext);
}
}
/*
* DEALTWITHBYDISCRETEMACHINERY
String keyPhrase = "DEALTWITHBYDISCRETEMACHINERY";
SolitaireCipher sc = new SolitaireCipher(keyPhrase);
Deck deck = sc.keyDeck;
System.out.println("DEALTWITHBYDISCRETEMACHINERY Deck before \n" + deck);
String outputtext = sc.decrypt(ingressCipher);
System.out.println("Decrypted value\n" + outputtext);
System.out.println("DEALTWITHBYDISCRETEMACHINERY Deck after \n" + deck);
*/
} // main(String[])
// Representation Data
// ===================
public Deck keyDeck;
// Constructors
// ============
public SolitaireCipher(Deck keyDeck) {
// Effect: creates a cipher with a key based on the
// order of the cards in the specified deck.
if (keyDeck != null) {
this.keyDeck = (Deck)keyDeck.clone();
} else this.keyDeck = new Deck(true);
} // SolitaireCipher(Deck)
public SolitaireCipher(String keyPhrase) {
// Effect: creates a cipher with a key based on the
// specified string.
keyDeck = new Deck(true);
// Shuffle deck according to key phrase.
for (int i = 0; i < keyPhrase.length(); i++) {
int cut_size = getCharValue(keyPhrase.charAt(i));
if (cut_size > 0) {
nextKeyStream();
keyDeck.tripleCut(1, keyDeck.count() - cut_size);
keyDeck.cutTop(1);
} // if (cut_size > 0)
} // for i
} // SolitaireCipher(String)
// Generic Methods
// ===============
public Object clone() {
return new SolitaireCipher(keyDeck);
} // clone()
public String toString() {
// Effect: return a string that represents the state of
// this cipher.
return keyDeck.toString();
} // toString()
// Deck Methods
// ============
public Deck getDeck() {
// Effect: return a copy of the current key deck.
return (Deck)keyDeck.clone();
} // getDeck()
// Utility Methods
// ===============
protected static int getCardValue(byte card) {
// Effect: returns a numeric value for the specified card.
if (Deck.isJoker(card)) {
return 53;
} else if (Deck.isClub(card)) {
return Deck.getFaceValue(card);
} else if (Deck.isDiamond(card)) {
return 13 + Deck.getFaceValue(card);
} else if (Deck.isHeart(card)) {
return 26 + Deck.getFaceValue(card);
} else if (Deck.isSpade(card)) {
return 39 + Deck.getFaceValue(card);
} else {
return 0; // Something bad happened.
} // if card
} // getCardValue(byte)
protected static int getCharValue(char c) {
// Effect: return the position of the specified character in
// the alphabet regardless of case, or zero if the character
// is not in the Latin alphabet.
if (c >= 'A' && c <= 'Z') {
return (int)c - 'A' + 1;
} else if (c >= 'a' && c <= 'z') {
return (int)c - 'a' + 1;
} else {
return 0;
} // if c
} // getCharValue(char)
protected static char getValueChar(int v) {
// Effect: return the position of the specified character in
// the alphabet regardless of case, or zero if the character
// is not in the Latin alphabet.
if (v >= 1 && v <= 26) {
return (char)((v - 1) + (int)'A');
} else {
return '*';
} // if c
} // getCharValue(char)
// Cryptographic Methods
// =====================
protected int nextKeyStream() {
// Effect: performs the basic stream encryption operation
// and returns the next element of the key stream.
byte jokerA = (byte)(Deck.JOKER | Deck.JOKER_A);
byte jokerB = (byte)(Deck.JOKER | Deck.JOKER_B);
// Step one: Move Joker A one card down.
keyDeck.moveDown(keyDeck.findTop(jokerA), 1);
// Step two: Move Joker B two cards down.
keyDeck.moveDown(keyDeck.findTop(jokerB), 2);
// Step three: Perform a triple cut.
int at_jokerA = keyDeck.findBottom(jokerA);
int at_jokerB = keyDeck.findBottom(jokerB);
keyDeck.tripleCut(Math.min(at_jokerA, at_jokerB),
Math.max(at_jokerA, at_jokerB) + 1);
// Step four: Perform a count cut.
byte count = keyDeck.peekBottom(0);
keyDeck.tripleCut(1, keyDeck.count() - getCardValue(count));
keyDeck.cutTop(1);
// Step five: Find the output card.
byte output = keyDeck.peekTop(getCardValue(keyDeck.peekTop(0)));
// Step six: Convert output card to a number.
int result = getCardValue(output);
if (result == 53) return nextKeyStream();
else if (result > 26) result = result - 26;
return result;
} // nextKeyStream()
public String decrypt(String ciphertext) {
// Effect: returns the ciphertext corresponding to the specified
// plaintext, according to the current key deck ordering.
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < ciphertext.length(); i++) {
int plain = getCharValue(ciphertext.charAt(i));
if (plain > 0) {
plain -= nextKeyStream();
if (plain < 1) plain += 26;
buffer.append(getValueChar(plain));
} // if (plain > 0)
} // for i
return buffer.toString();
} // decrypt(String)
public String encrypt(String plaintext) {
// Effect: returns the ciphertext corresponding to the specified
// plaintext, according to the current key deck ordering. Any
// string with a length that is not a multiple of five will be
// padded with the character 'X'.
return encrypt(plaintext, true);
} // encrypt(String)
public String encrypt(String plaintext, boolean padded) {
// Effect: returns the ciphertext corresponding to the specified
// plaintext, according to the current key deck ordering.
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < plaintext.length(); i++) {
int cipher = getCharValue(plaintext.charAt(i));
if (cipher > 0) {
cipher += nextKeyStream();
if (cipher > 26) cipher -= 26;
buffer.append(getValueChar(cipher));
} // if (cipher > 0)
} // for i
if (padded) {
while (buffer.length() % 5 != 0) {
buffer.append(encrypt("X", false));
} // while buffer
} // if (padded)
return buffer.toString();
} // encrypt(String, boolean)
} // class SolitaireCipher
class Deck
implements Serializable, Cloneable
{
// Constants
// =========
// Card Masking Constants
protected static final byte MASK_SUIT = (byte)0xF0;
protected static final byte MASK_VALUE = (byte)0x0F;
protected static final byte MASK_BLACK = (byte)0x20;
protected static final byte MASK_RED = (byte)0x40;
protected static final byte MASK_JOKER = (byte)0x80;
// Card Suit Constants
public static final byte CLUBS = (byte)0x20;
public static final byte DIAMONDS = (byte)0x40;
public static final byte HEARTS = (byte)0x50;
public static final byte SPADES = (byte)0x30;
public static final byte JOKER = (byte)0x90;
// Card Value Constants
public static final byte ACE = (byte)0x01;
public static final byte DEUCE = (byte)0x02;
public static final byte THREE = (byte)0x03;
public static final byte FOUR = (byte)0x04;
public static final byte FIVE = (byte)0x05;
public static final byte SIX = (byte)0x06;
public static final byte SEVEN = (byte)0x07;
public static final byte EIGHT = (byte)0x08;
public static final byte NINE = (byte)0x09;
public static final byte TEN = (byte)0x0A;
public static final byte JACK = (byte)0x0B;
public static final byte QUEEN = (byte)0x0C;
public static final byte KING = (byte)0x0D;
public static final byte JOKER_A = (byte)0x0E;
public static final byte JOKER_B = (byte)0x0F;
// Representation Data
// ===================
protected byte cards[]; // The contents of the deck.
protected int marker; // Indicates the top.
// Constructors
// ============
public Deck() {
// Effect: create a deck of cards in bridge sorted order
// with jokers at the end.
this(true);
} // Deck()
public Deck(boolean useJokers) {
// Effect: create a deck of cards in bridge sorted order.
// Jokers will be included only if useJokers is true.
marker = useJokers? 54 : 52;
cards = new byte[54];
// Place the cards in order.
for (int i = 0; i < 4; i++) {
// Determine suit (using bridge ordering).
byte suit = JOKER;
switch (i) {
case 0: suit = CLUBS; break;
case 1: suit = DIAMONDS; break;
case 2: suit = HEARTS; break;
case 3: suit = SPADES; break;
default:
// This should not happen.
break;
}
for (int j = 0; j < 13; j++) {
cards[(marker - 1) - ((13 * i) + j)] =
(byte)(suit | (j+1));
} // for j
} // for i
// Place the jokers
cards[useJokers ? 1 : marker + 1] = JOKER | JOKER_A;
cards[useJokers ? 0 : marker] = JOKER | JOKER_B;
} // Deck()
public Deck(byte cards[]) {
// Effect: create a deck of cards with the order and
// content specified by the cards array.
marker = cards.length;
this.cards = new byte[marker];
for (int i = 0; i < cards.length; i++) {
this.cards[i] = cards[i];
} // for i
}
protected Deck(Deck d) {
// Effect: create a deck of cards identical to the
// specified deck.
marker = d.marker;
cards = new byte[d.cards.length];
for (int i = 0; i < 54; i++) {
cards[i] = d.cards[i];
} // for i
} // Deck(Deck)
// Generic Methods
// ===============
public Object clone() {
// Effect: returns a deck that is identical to this one.
return new Deck(this);
} // clone()
public String toString() {
// Effect: returns a string that represents the cards in
// this deck, included from the bottom of the deck to
// the top. This is meant to correspond to how a
// person might look through an actual deck of cards
// after turning it over.
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < marker; i++) {
buffer.append(cardString(cards[i]));
// Add line breaks after every thirteen cards.
if (i % 13 == 12)
buffer.append("\n");
else buffer.append(" ");
} // for i
// Add a final linefeed if necessary.
if (marker % 13 != 0)
buffer.append("\n");
return buffer.toString();
} // toString()
// Deck Manipulation Methods (Mutators)
// =========================
public void shuffle() {
// Effect: changes the order of the cards to a random
// pattern.
shuffle(new Random());
} // shuffle()
public void shuffle(Random rand) {
// Effect: changes the order of the cards to a random
// pattern using the specified random number generator.
for (int i = 0; i < marker; i++) {
int swap = Math.abs(rand.nextInt() % marker);
byte buffer = cards[i];
cards[i] = cards[swap];
cards[swap] = buffer;
} // for i
} // shuffle(Random)
public byte deal() {
// Effect: return the top card and remove it from the deck
// if any cards are left in the deck. Otherwise return zero.
return (marker > 0) ? cards[--marker] : (byte)0;
} // deal()
public void collect() {
// Effect: restore cards that have been dealt to the deck.
marker = cards.length;
} // collect()
protected void basicCut(int size, int limit) {
// Effect: switch cards below the specified position with
// the cards above, up to the specified limit.
// A size of zero or limit will not change the deck.
if (size > 0 && size < limit) {
byte buffer[] = new byte[limit];
// First move the lower cards up.
for (int i = 0; i < size; i++) {
buffer[i + (limit - size)] = cards[i];
} // for i
// Then move the upper cards down.
for (int i = size; i < limit; i++) {
buffer[i - size] = cards[i];
} // for i
// Then replace the original deck.
for (int i = 0; i < limit; i++) {
cards[i] = buffer[i];
} // for i
} // if position
} // basicCut(int, int)
public void cutTop(int size) {
// Effect: switch cards below the specified position with the
// cards above.
// A size of zero or marker will not change the deck.
basicCut(marker - size, marker);
} // cutTop(int)
public void cutBottom(int size) {
// Effect: switch cards below the specified position with the
// cards above.
// A size of zero or marker will not change the deck.
basicCut(size, marker);
} // cutBottom(int)
public void tripleCut(int low, int high) {
// Requires: 0 < low < high < this.count()
// Effect: swaps the cards above the highest of the
// specified positions with cards below the lowest.
if ((0 <= low) && (low <= high) && (high <= marker)) {
basicCut(low, high);
basicCut(high, marker);
} // if low && high
} // tripleCut();
public void moveDown(int position) {
// Effect: swaps the card at the specified position with
// the card below it, wrapping around the end of the
// deck.
moveDown(position, 1);
} // moveDown(int)
public void moveDown(int position, int count) {
// Effect: swaps the card at the specified position with
// the card below it, wrapping around the end of the
// deck. If count is greater than zero then this
// operation is repeated that many times.
int index = (marker - 1) - position;
while (count > 0) {
if (index > 0 && index < marker) {
byte buffer = cards[index];
cards[index] = cards[index - 1];
cards[index - 1] = buffer;
index--;
} else if (index == 0) {
byte buffer;
for (int i = 0; i < marker - 2; i++) {
buffer = cards[i];
cards[i] = cards[i + 1];
cards[i + 1] = buffer;
index++;
} // for i
} // if position
count--;
} // while (count > 0)
} // moveDown(int)
// Deck Inspection Methods (Non-Mutators)
// =======================
public int count() {
// Effect: return the number of cards in this deck.
return marker;
} // count()
public byte peekTop(int position) {
// Effect: return the card at the specified position, but do
// not remove it from the deck. The top card has position
// zero, the next card position one, and so on.
if (position >= 0 && position < marker) {
return cards[(marker - 1) - position];
} else return (byte)0;
} // peekTop(int)
public byte peekBottom(int position) {
// Effect: return the card at the specified position, but do
// not remove it from the deck. The top card has position
// zero, the next card position one, and so on.
if (position >= 0 && position < marker) {
return cards[position];
} else return (byte)0;
} // peekBottom(int)
public int findTop(byte card) {
// Effect: return the position of the specified card if it can
// be found in the deck. Otherwise return a negative number.
int i = 0;
while (i < marker) {
if (cards[i] == card) {
return (marker - 1) - i;
} // if (cards[i] == card)
i++;
} // while (i < marker)
return -1;
} // findTop(byte)
public int findBottom(byte card) {
// Effect: return the position of the specified card if it can
// be found in the deck. Otherwise return a negative number.
int i = 0;
while (i < marker) {
if (cards[i] == card) {
return i;
} // if (cards[i] == card)
i++;
} // while (i < marker)
return -1;
} // findBottom(byte)
// Card Methods
// ============
public static String
cardString(byte card) {
// Effect: return a string that represenets the specified
// card.
StringBuffer buffer = new StringBuffer();
// Mark the card value.
switch (card & MASK_VALUE) {
case ACE:
buffer.append("A");
break;
case DEUCE:
buffer.append("2");
break;
case THREE:
buffer.append("3");
break;
case FOUR:
buffer.append("4");
break;
case FIVE:
buffer.append("5");
break;
case SIX:
buffer.append("6");
break;
case SEVEN:
buffer.append("7");
break;
case EIGHT:
buffer.append("8");
break;
case NINE:
buffer.append("9");
break;
case TEN:
buffer.append("0");
break;
case JACK:
buffer.append("J");
break;
case QUEEN:
buffer.append("Q");
break;
case KING:
buffer.append("K");
break;
case JOKER_A:
buffer.append("A");
break;
case JOKER_B:
buffer.append("B");
break;
default:
// This should not happen.
break;
} // switch (card & MASK_VALUE)
// Mark the card suit.
switch (card & MASK_SUIT) {
case CLUBS:
buffer.append("C");
break;
case DIAMONDS:
buffer.append("D");
break;
case HEARTS:
buffer.append("H");
break;
case SPADES:
buffer.append("S");
break;
case JOKER:
buffer.append("J");
break;
default:
break;
} // switch (card & MASK_SUIT)
return buffer.toString();
} // cardString(byte)
// Suit Methods
public static boolean
isClub(byte card) { return (card & MASK_SUIT) == CLUBS; }
public static boolean
isDiamond(byte card) { return (card & MASK_SUIT) == DIAMONDS; }
public static boolean
isHeart(byte card) { return (card & MASK_SUIT) == HEARTS; }
public static boolean
isSpade(byte card) { return (card & MASK_SUIT) == SPADES; }
// Type Methods
public static boolean
isBlack(byte card) { return (card & MASK_BLACK) != (byte)0; }
public static boolean
isRed(byte card) { return (card & MASK_RED) != (byte)0; }
public static boolean
isJoker(byte card) { return (card & MASK_JOKER) != (byte)0; }
// Joker Methods
public static boolean
isJokerA(byte card) { return card == (JOKER | JOKER_A); }
public static boolean
isJokerB(byte card) { return card == (JOKER | JOKER_B); }
// Value Methods
public static boolean
isAce(byte card) { return (card & MASK_VALUE) == ACE; }
public static boolean
isDuece(byte card) { return (card & MASK_VALUE) == DEUCE; }
public static boolean
isTwo(byte card) { return isDuece(card); }
public static boolean
isThree(byte card) { return (card & MASK_VALUE) == THREE; }
public static boolean
isFour(byte card) { return (card & MASK_VALUE) == FOUR; }
public static boolean
isFive(byte card) { return (card & MASK_VALUE) == FIVE; }
public static boolean
isSix(byte card) { return (card & MASK_VALUE) == SIX; }
public static boolean
isSeven(byte card) { return (card & MASK_VALUE) == SEVEN; }
public static boolean
isEight(byte card) { return (card & MASK_VALUE) == EIGHT; }
public static boolean
isNine(byte card) { return (card & MASK_VALUE) == NINE; }
public static boolean
isTen(byte card) { return (card & MASK_VALUE) == TEN; }
public static boolean
isJack(byte card) { return (card & MASK_VALUE) == JACK; }
public static boolean
isQueen(byte card) { return (card & MASK_VALUE) == QUEEN; }
public static boolean
isKing(byte card) { return (card & MASK_VALUE) == KING; }
public static int
getFaceValue(byte card) {return (int)(card & MASK_VALUE); }
// Module Test Method
// ==================
// /public static void main(String args[]) {
// // Effect: executes a test of this module.
// Deck d = new Deck(false);
// System.out.println("Default Deck:");
// System.out.println(d.toString());
// System.out.println(); // */
//
// d.shuffle();
// System.out.println("Shuffled Deck:");
// System.out.println(d.toString());
// System.out.println(); // */
// } // main(String[])*/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment