//RSAdemo.java //Roger Morrison //March 14, 2002 //This applet allows a user to create RSA keys and encrypt/decrypt numbers. // It is for educational purposes only and is not industrial strength for lots of // reasons. For starters is it designed by a first time Java programmer, // it also uses basic psuedo-random number generation. // import java.awt.*; import java.applet.*; import java.awt.event.*; import java.math.BigInteger; import java.util.Random; //I'm using this main applet as the Action Listener mainly because // I don't know how to implement it in a separate class and I gave // up trying. So currently I can only use buttons in the applet. public class RSAdemo extends java.applet.Applet implements ActionListener { //I initialized the controls in the same order they appear on the page Label pqLabel; TextArea pTextArea; Label pLabel; TextArea qTextArea; Label qLabel; Button generate_pqButton; Label generate_pq_sizeLabel; TextField generate_pq_sizeField; Label nLabel; TextArea nTextArea; Label nSpacer; Button calculate_nButton; Label eLabel; TextArea eTextArea; Label eSpacer; Button generate_eButton; Label generate_e_sizeLabel; TextField generate_e_sizeField; Label dLabel; TextArea dTextArea; Label dSpacer; Button calculate_dButton; Label useSpacer; TextArea plainTextArea; Button convert_to_numberButton; Label convertButtonSpacer; Button convert_to_textButton; TextArea mTextArea; Button encryptButton; Label cryptButtonSpacer; Button decryptButton; TextArea cTextArea; // This is the certainty that the bigInteger class will generate a prime number. It's currently // set at 20 which means the odds of being a prime is 1-2^20 or about one in a million of it being // non-prime. If this number is increased the time to generate a prime goes up. int prime_certainty = 20; public void init() { //The white background matches the web page background setBackground (Color.white); //The flowLayout is left justified. I used 0 instead of LEFT because I couldn't get the // LEFT constant to work. setLayout(new FlowLayout(0)); //I really struggled with the layout of this applet. It took almost as long to // get the UI looking right than it did to program the RSA. I tried static placement // using .setBounds but then everything would look the wrong size on different // platforms (the correct size on HP-Unix Netscape looks bad on a Mac or PC). Also, // because everything changes size on different platforms, I couldn't rely on control // placement on the screen. So I ended up using FlowLayout and tricking it into putting // items on different lines by packaging a group of components on into it's own panel. // If anyone has a better way to do this please email me. pqLabel = new Label("Enter prime 'p' and 'q' values or use the button below to generate them:"); add(pqLabel); Panel pPanel = new Panel(); pLabel = new Label("p:"); pPanel.add(pLabel); pTextArea = new TextArea("", 2, 50, 1); pPanel.add(pTextArea); add(pPanel); Panel qPanel = new Panel(); qLabel = new Label("q:"); qPanel.add(qLabel); qTextArea = new TextArea("", 2, 50, 1); qPanel.add(qTextArea); add(qPanel); Panel generate_pqPanel = new Panel(); generate_pqButton = new Button("Generate p and q"); generate_pqPanel.add(generate_pqButton); generate_pq_sizeLabel = new Label("which are of average bit size:"); generate_pqPanel.add(generate_pq_sizeLabel); generate_pq_sizeField = new TextField("16", 5); generate_pqPanel.add(generate_pq_sizeField); add(generate_pqPanel); Panel nTextPanel = new Panel(); nLabel = new Label("n:"); nTextPanel.add(nLabel); nTextArea = new TextArea("", 4, 50, 1); nTextPanel.add(nTextArea); add(nTextPanel); Panel nPanel = new Panel(); calculate_nButton = new Button("Calculate n"); nPanel.add(calculate_nButton); nSpacer = new Label(" "); nPanel.add(nSpacer); add(nPanel); Panel eTextPanel = new Panel(); eLabel = new Label("e:"); eTextPanel.add(eLabel); eTextArea = new TextArea("", 2, 50, 1); eTextPanel.add(eTextArea); add (eTextPanel); Panel ePanel = new Panel(); generate_eButton = new Button("Generate e"); ePanel.add(generate_eButton); generate_e_sizeLabel = new Label("which is of bit size:"); ePanel.add(generate_e_sizeLabel); generate_e_sizeField = new TextField("8", 5); ePanel.add(generate_e_sizeField); eSpacer = new Label(" "); ePanel.add(eSpacer); add(ePanel); Panel dTextPanel = new Panel(); dLabel = new Label("d:"); dTextPanel.add(dLabel); dTextArea = new TextArea("", 2, 50, 1); dTextPanel.add(dTextArea); add(dTextPanel); Panel dPanel = new Panel(); calculate_dButton = new Button("Calculate d"); dPanel.add(calculate_dButton); dSpacer = new Label(" "); dPanel.add(dSpacer); add(dPanel); useSpacer = new Label("Enter text, numbers or encoded numbers below. "); add(useSpacer); plainTextArea = new TextArea("Plain text message", 4, 54, 1); add(plainTextArea); Panel convertButtonPanel = new Panel(); convert_to_numberButton = new Button("Convert to Number"); convertButtonPanel.add(convert_to_numberButton); convertButtonSpacer = new Label(" "); convertButtonPanel.add(convertButtonSpacer); convert_to_textButton = new Button("Convert to Text"); convertButtonPanel.add(convert_to_textButton); add(convertButtonPanel); mTextArea = new TextArea("Numerical message", 4, 54, 1); add(mTextArea); Panel cryptButtonPanel = new Panel(); encryptButton = new Button("Encrypt") ; cryptButtonPanel.add(encryptButton); cryptButtonSpacer = new Label(" "); cryptButtonPanel.add(cryptButtonSpacer); decryptButton = new Button("Decrypt") ; cryptButtonPanel.add(decryptButton); add(cryptButtonPanel); cTextArea = new TextArea("Encoded numerical message", 4, 54, 1); add(cTextArea); generate_pqButton.addActionListener(this); calculate_nButton.addActionListener(this); generate_eButton.addActionListener(this); calculate_dButton.addActionListener(this); convert_to_numberButton.addActionListener(this); convert_to_textButton.addActionListener(this); encryptButton.addActionListener(this); decryptButton.addActionListener(this); } public void actionPerformed(ActionEvent event) { //If the "generate pq" button is pushed then get the desired size of p and q // then let the BigInteger class generate the prime. The size of p and q // is offset so that we can guarantee that p and q will not be too close // to each other. This make guessing p and q by searching values next to the // square root of n more difficult. if (event.getSource() == generate_pqButton) { int pq_size = new Integer(generate_pq_sizeField.getText()).intValue(); if (pq_size >= 4) { qTextArea.setText(new BigInteger(pq_size + 1, prime_certainty, new Random()).toString()); pTextArea.setText(new BigInteger(pq_size - 1, prime_certainty, new Random()).toString()); } else { pTextArea.setText("Enter larger p and q size."); } } //Send the data from p and q to the calculate n function else if (event.getSource() == calculate_nButton) { nTextArea.setText(calculate_n(new BigInteger(pTextArea.getText()), new BigInteger(qTextArea.getText())) .toString()); } //Send the data from p, q and the size of e to the generate_e function else if (event.getSource() == generate_eButton) { eTextArea.setText(generate_e(new BigInteger(pTextArea.getText()), new BigInteger(qTextArea.getText()), new Integer(generate_e_sizeField.getText()).intValue()) .toString()); } //Send data from p, q and e to the calculate_d function else if (event.getSource() == calculate_dButton) { dTextArea.setText( calculate_d(new BigInteger(pTextArea.getText()), new BigInteger(qTextArea.getText()), new BigInteger(eTextArea.getText())) .toString()); } //This converts the plain text string into a number by reading the string in, // converting to bytes (ascii), then converting these bytes into a BigInteger. //The opposite happens in the next function. else if (event.getSource() == convert_to_numberButton) { mTextArea.setText(new BigInteger(plainTextArea.getText().getBytes()).toString()); } else if (event.getSource() == convert_to_textButton) { plainTextArea.setText(new String(new BigInteger(mTextArea.getText()).toByteArray())); } //Send the data to be encrypted and decrypted. else if (event.getSource() == encryptButton) { cTextArea.setText(encrypt(new BigInteger(mTextArea.getText()), new BigInteger(eTextArea.getText()), new BigInteger(nTextArea.getText())) .toString()); } else if (event.getSource() == decryptButton) { mTextArea.setText(decrypt(new BigInteger(cTextArea.getText()), new BigInteger(dTextArea.getText()), new BigInteger(nTextArea.getText())) .toString()); } } //To generate e first phi(pq) (which is equal to phi(n)) is calculated. // This is equal to (p-1)*(q-1). Then the loop searches for psudo-randomly // generated e of a specified size until one is found that is relatively // prime to phi(pq) (gdc(e,phi_pq) = 1). I used the generate random // prime function because it guarantees a specific bit size of the number // it returns. The regular method of generating a psuedo-random number // only guarantees the number is between 0 and 2^n-1. Another way would // be just add a one to the front of a random number, but I was too lazy // to do that. So I set the primality certainty to 0 because I don't // care if e is prime. //The last line is to ensure it does not go into a infinite loop // if a e cannot be found for that bit size. BigInteger generate_e(BigInteger p, BigInteger q, int bitsize) { BigInteger e, phi_pq; e = new BigInteger("0"); phi_pq = q.subtract(new BigInteger("1")); phi_pq = phi_pq.multiply(p.subtract(new BigInteger("1"))); int i = 0; do { e = (new BigInteger(bitsize, 0, new Random())).setBit(0); i = i + 1; } while( i<100 && (e.gcd(phi_pq).compareTo(new BigInteger("1")) != 0)); return e; } //Once again the BigInteger class saves the programmer a lot of work. // I calculate phi_pq ((p-1)*(q-1)) and then let the modInverse function // do the hard part of finding e^(-1) mod phi_pq. BigInteger calculate_d(BigInteger p, BigInteger q, BigInteger e) { BigInteger d, phi_pq; phi_pq = q.subtract(new BigInteger("1")); phi_pq = phi_pq.multiply(p.subtract(new BigInteger("1"))); d = e.modInverse(phi_pq); return d; } //Returns n=p*q BigInteger calculate_n(BigInteger p, BigInteger q) { return p.multiply(q); } //This is a little tricky because the user is allowed to choose // the size of n. The value to be encrypted must be less than n. // So first I find the bit size of n, then subract one, and that is the // size of the message that I will encrypt at one time. This ensures // the message chunk that is encrypted is smaller than n. I use a // mask to take one chunk of message at a time. Then the chunk is // encrypted and placed in the result c. During the next iteration // the message is shifted right and the result is shifted left and combined // with c. The encrypted chunk must be the same bit size as n so that no // data is lost. // Encryption is done using the modPow function provide by the BigInt class. BigInteger encrypt(BigInteger m, BigInteger e, BigInteger n) { BigInteger c, bitmask; c = new BigInteger("0"); int i = 0; bitmask = (new BigInteger("2")).pow(n.bitLength()-1).subtract(new BigInteger("1")); while (m.compareTo(bitmask) == 1) { c = m.and(bitmask).modPow(e,n).shiftLeft(i*n.bitLength()).or(c); m = m.shiftRight(n.bitLength()-1); i = i+1; } c = m.modPow(e,n).shiftLeft(i*n.bitLength()).or(c); return c; } //Decryption is done just as encryption above, only now the data is read in // in chunks the same size as n, and the result, if correct, will be one bit // less than the size of n (because that was the original chuck size). BigInteger decrypt(BigInteger c, BigInteger d, BigInteger n) { BigInteger m, bitmask; m = new BigInteger("0"); int i = 0; bitmask = (new BigInteger("2")).pow(n.bitLength()).subtract(new BigInteger("1")); while (c.compareTo(bitmask) == 1) { m = c.and(bitmask).modPow(d,n).shiftLeft(i*(n.bitLength()-1)).or(m); c = c.shiftRight(n.bitLength()); i = i+1; } m = c.modPow(d,n).shiftLeft(i*(n.bitLength()-1)).or(m); return m; } }