Skip to content

Commit

Permalink
Implemented ECDSA Key support.
Browse files Browse the repository at this point in the history
  • Loading branch information
iiordanov committed Nov 13, 2024
1 parent 17d633d commit 1b11cf5
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@

package com.iiordanov.pubkeygenerator;

import static com.iiordanov.pubkeygenerator.PubkeyDatabase.KEY_TYPE_DSA;
import static com.iiordanov.pubkeygenerator.PubkeyDatabase.KEY_TYPE_ECDSA;
import static com.iiordanov.pubkeygenerator.PubkeyDatabase.KEY_TYPE_RSA;

import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
Expand Down Expand Up @@ -57,6 +61,9 @@
public class GeneratePubkeyActivity extends Activity implements OnEntropyGatheredListener {
public final static String TAG = "GeneratePubkeyActivity";

final static int DEFAULT_BITS_ECDSA = 521;
final static int MAX_BITS_ECDSA = 521;

final static int MIN_BITS_RSA = 768;
final static int DEFAULT_BITS_RSA = 2048;
final static int MAX_BITS_RSA = 4096;
Expand All @@ -75,13 +82,14 @@ public class GeneratePubkeyActivity extends Activity implements OnEntropyGathere
private Button share;
private Button decrypt;
private Button copy;
private Button copyPriv;
private Button save;
private Dialog entropyDialog;
private ProgressDialog progress;
private EditText password1;
private String keyType = PubkeyDatabase.KEY_TYPE_RSA;
private int minBits = MIN_BITS_RSA;
private int bits = DEFAULT_BITS_RSA;
private String keyType = KEY_TYPE_ECDSA;
private int minBits = DEFAULT_BITS_ECDSA;
private int bits = DEFAULT_BITS_ECDSA;
private byte[] entropy;
// Variables we use to receive (from calling activity)
// and recover all key-pair related information.
Expand All @@ -104,6 +112,7 @@ public void onTextChanged(CharSequence s, int start, int before,
};
private KeyPair kp = null;
private String publicKeySSHFormat;
private String privateKeySSHFormat;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Expand Down Expand Up @@ -145,14 +154,17 @@ public void onCreate(Bundle icicle) {
keyTypeGroup = (RadioGroup) findViewById(R.id.key_type);

bitsText = (EditText) findViewById(R.id.bits);
bitsText.setText(String.valueOf(DEFAULT_BITS_ECDSA));
bitsSlider = (SeekBar) findViewById(R.id.bits_slider);
bitsSlider.setEnabled(false);

password1 = (EditText) findViewById(R.id.password);

generate = (Button) findViewById(R.id.generate);
share = (Button) findViewById(R.id.share);
decrypt = (Button) findViewById(R.id.decrypt);
copy = (Button) findViewById(R.id.copy);
copyPriv = (Button) findViewById(R.id.copyPriv);
save = (Button) findViewById(R.id.save);
importKey = (Button) findViewById(R.id.importKey);

Expand All @@ -173,27 +185,20 @@ public void onCreate(Bundle icicle) {

public void onCheckedChanged(RadioGroup group, int checkedId) {
if (checkedId == R.id.rsa) {
keyType = KEY_TYPE_RSA;
minBits = MIN_BITS_RSA;

bitsSlider.setEnabled(true);
bitsSlider.setProgress(DEFAULT_BITS_RSA);
bitsSlider.setMax(MAX_BITS_RSA - minBits);

bitsText.setText(String.valueOf(DEFAULT_BITS_RSA));
bitsText.setEnabled(true);

keyType = PubkeyDatabase.KEY_TYPE_RSA;
setValuesForKeyType(true, DEFAULT_BITS_RSA, MAX_BITS_RSA - minBits);
} else if (checkedId == R.id.dsa) {
keyType = KEY_TYPE_DSA;
minBits = MIN_BITS_DSA;

bitsSlider.setEnabled(true);
bitsSlider.setProgress(DEFAULT_BITS_DSA);
bitsSlider.setMax(MAX_BITS_DSA - minBits);
setValuesForKeyType(true, DEFAULT_BITS_DSA, MAX_BITS_DSA - minBits);
} else if (checkedId == R.id.ecdsa) {
keyType = KEY_TYPE_ECDSA;
minBits = 0;

bitsText.setText(String.valueOf(DEFAULT_BITS_DSA));
bitsText.setEnabled(true);

keyType = PubkeyDatabase.KEY_TYPE_DSA;
setValuesForKeyType(false, DEFAULT_BITS_ECDSA, MAX_BITS_ECDSA);
}
}
});
Expand All @@ -203,13 +208,16 @@ public void onCheckedChanged(RadioGroup group, int checkedId) {
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromTouch) {
// Stay evenly divisible by 8 because it looks nicer to have
// 2048 than 2043 bits.
// 2048 than 2043 bits, except for Elliptic Curves

int leftover = progress % 8;
int ourProgress = progress;

if (leftover > 0)
ourProgress += 8 - leftover;
if (!KEY_TYPE_ECDSA.equals(keyType)) {
int leftover = progress % 8;
if (leftover > 0) {
ourProgress += 8 - leftover;
}
}

bits = minBits + ourProgress;
bitsText.setText(String.valueOf(bits));
Expand Down Expand Up @@ -278,6 +286,14 @@ public void onClick(View view) {
}
});

copyPriv.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
hideSoftKeyboard(view);
cm.setText(privateKeySSHFormat);
Toast.makeText(getBaseContext(), getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show();
}
});

save.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
hideSoftKeyboard(view);
Expand Down Expand Up @@ -306,6 +322,15 @@ public void onClick(View view) {
});
}

private void setValuesForKeyType(boolean enabled, int defaultBitsEcdsa, int maxBitsEcdsa) {
bitsSlider.setEnabled(enabled);
bitsSlider.setProgress(defaultBitsEcdsa);
bitsSlider.setMax(maxBitsEcdsa);

bitsText.setText(String.valueOf(defaultBitsEcdsa));
bitsText.setEnabled(true);
}

/**
* Hides the soft keyboard.
*/
Expand All @@ -328,6 +353,7 @@ public boolean decryptAndRecoverKey() {
} else {
try {
publicKeySSHFormat = PubkeyUtils.convertToOpenSSHFormat(kp.getPublic(), null);
privateKeySSHFormat = "-----BEGIN PRIVATE KEY-----\n" + Base64.encodeToString(kp.getPrivate().getEncoded(), Base64.DEFAULT) + "\n-----END PRIVATE KEY-----";
} catch (Exception e) {
e.printStackTrace();
success = false;
Expand Down Expand Up @@ -429,6 +455,8 @@ private void convertToBase64AndSendIntent(KeyPair pair) throws Exception {
Log.d(TAG, "public: " + PubkeyUtils.formatKey(pub));
sshPrivKey = Base64.encodeToString(PubkeyUtils.getEncodedPrivate(priv, secret), Base64.DEFAULT);
sshPubKey = Base64.encodeToString(PubkeyUtils.getEncodedPublic(pub), Base64.DEFAULT);
Log.d(TAG, "sshPrivKey: " + sshPrivKey);
Log.d(TAG, "sshPubKey: " + sshPubKey);

// Send the generated data back to the calling activity.
Intent databackIntent = new Intent();
Expand Down Expand Up @@ -457,7 +485,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
convertToBase64AndSendIntent(pair);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Failed to decode key.");
Log.e(TAG, "Failed to import key.");
Toast.makeText(getBaseContext(), e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@
public class PubkeyBean extends AbstractBean {
public static final String BEAN_NAME = "pubkey";

private static final String KEY_TYPE_RSA = "RSA";

private static final String KEY_TYPE_DSA = "DSA";
private static final String KEY_TYPE_ECDSA = PubkeyDatabase.KEY_TYPE_ECDSA;
private static final String KEY_TYPE_RSA = PubkeyDatabase.KEY_TYPE_RSA;
private static final String KEY_TYPE_DSA = PubkeyDatabase.KEY_TYPE_DSA;

/* Database fields */
private long id;
Expand Down Expand Up @@ -125,6 +125,11 @@ public void setPublicKey(byte[] encoded) {
publicKey = decodePublicKeyAs(pubKeySpec, KEY_TYPE_DSA);
if (publicKey != null) {
type = KEY_TYPE_DSA;
} else {
publicKey = decodePublicKeyAs(pubKeySpec, KEY_TYPE_ECDSA);
if (publicKey != null) {
type = KEY_TYPE_ECDSA;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public class PubkeyDatabase extends RobustSQLiteOpenHelper {
public final static String FIELD_PUBKEY_CONFIRMUSE = "confirmuse";
public final static String FIELD_PUBKEY_LIFETIME = "lifetime";

public final static String KEY_TYPE_RSA = "RSA",
public final static String KEY_TYPE_ECDSA = "EC",
KEY_TYPE_RSA = "RSA",
KEY_TYPE_DSA = "DSA",
KEY_TYPE_IMPORTED = "IMPORTED";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,23 @@

package com.iiordanov.pubkeygenerator;

import static com.iiordanov.pubkeygenerator.PubkeyDatabase.KEY_TYPE_DSA;
import static com.iiordanov.pubkeygenerator.PubkeyDatabase.KEY_TYPE_ECDSA;
import static com.iiordanov.pubkeygenerator.PubkeyDatabase.KEY_TYPE_RSA;

import android.content.Context;
import android.util.Log;

import com.trilead.ssh2.crypto.Base64;
import com.trilead.ssh2.crypto.PEMDecoder;
import com.trilead.ssh2.crypto.PEMStructure;
import com.trilead.ssh2.signature.DSASHA1Verify;
import com.trilead.ssh2.signature.ECDSASHA2Verify;
import com.trilead.ssh2.signature.RSASHA1Verify;

import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.math.ec.ECPoint;

import java.io.IOException;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
Expand All @@ -43,6 +51,7 @@
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.DSAPublicKeySpec;
Expand Down Expand Up @@ -165,36 +174,52 @@ public static PublicKey decodePublic(byte[] encoded, String keyType) throws NoSu

public static KeyPair recoverKeyPair(byte[] encoded) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
KeySpec privKeySpec = new PKCS8EncodedKeySpec(encoded);
KeySpec pubKeySpec;

PrivateKey priv;
PublicKey pub;
KeyFactory kf;
KeyPair keyPair;
try {
kf = KeyFactory.getInstance(PubkeyDatabase.KEY_TYPE_RSA, new org.bouncycastle.jce.provider.BouncyCastleProvider());
priv = kf.generatePrivate(privKeySpec);

pubKeySpec = new RSAPublicKeySpec(((RSAPrivateCrtKey) priv)
.getModulus(), ((RSAPrivateCrtKey) priv)
.getPublicExponent());

pub = kf.generatePublic(pubKeySpec);
} catch (ClassCastException e) {
kf = KeyFactory.getInstance(PubkeyDatabase.KEY_TYPE_DSA, new org.bouncycastle.jce.provider.BouncyCastleProvider());
priv = kf.generatePrivate(privKeySpec);

DSAParams params = ((DSAPrivateKey) priv).getParams();

// Calculate public key Y
BigInteger y = params.getG().modPow(((DSAPrivateKey) priv).getX(),
params.getP());
keyPair = getRsaKeyPair(privKeySpec);
} catch (Exception e) {
try {
keyPair = getDsaKeyPair(privKeySpec);
} catch (Exception e2) {
keyPair = getEcdsaKeyPair(privKeySpec);
}
}
return keyPair;
}

pubKeySpec = new DSAPublicKeySpec(y, params.getP(), params.getQ(),
params.getG());
private static KeyPair getEcdsaKeyPair(KeySpec privKeySpec) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory kf = KeyFactory.getInstance(KEY_TYPE_ECDSA, new org.bouncycastle.jce.provider.BouncyCastleProvider());
PrivateKey priv = kf.generatePrivate(privKeySpec);
BigInteger d = ((BCECPrivateKey) priv).getD();
org.bouncycastle.jce.spec.ECParameterSpec ecSpec =
((BCECPrivateKey) priv).getParameters();
ECPoint Q = ((BCECPrivateKey) priv).getParameters().getG().multiply(d);
org.bouncycastle.jce.spec.ECPublicKeySpec pubSpec = new
org.bouncycastle.jce.spec.ECPublicKeySpec(Q, ecSpec);
PublicKey pub = kf.generatePublic(pubSpec);
return new KeyPair(pub, priv);
}

pub = kf.generatePublic(pubKeySpec);
}
private static KeyPair getDsaKeyPair(KeySpec privKeySpec) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory kf = KeyFactory.getInstance(KEY_TYPE_DSA, new org.bouncycastle.jce.provider.BouncyCastleProvider());
PrivateKey priv = kf.generatePrivate(privKeySpec);
DSAParams params = ((DSAPrivateKey) priv).getParams();
// Calculate public key Y
BigInteger y = params.getG().modPow(((DSAPrivateKey) priv).getX(),
params.getP());
KeySpec pubKeySpec = new DSAPublicKeySpec(y, params.getP(), params.getQ(),
params.getG());
PublicKey pub = kf.generatePublic(pubKeySpec);
return new KeyPair(pub, priv);
}

private static KeyPair getRsaKeyPair(KeySpec privKeySpec) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory kf = KeyFactory.getInstance(KEY_TYPE_RSA, new org.bouncycastle.jce.provider.BouncyCastleProvider());
PrivateKey priv = kf.generatePrivate(privKeySpec);
KeySpec pubKeySpec = new RSAPublicKeySpec(((RSAPrivateCrtKey) priv)
.getModulus(), ((RSAPrivateCrtKey) priv)
.getPublicExponent());
PublicKey pub = kf.generatePublic(pubKeySpec);
return new KeyPair(pub, priv);
}

Expand Down Expand Up @@ -249,7 +274,7 @@ public static KeyPair decryptAndRecoverKeyPair(String sshPrivKey, String passphr
android.util.Base64.DEFAULT));
}
} catch (Exception e) {
Log.i(TAG, "Either key is not encrypted and we were given passphrase, or the passphrase is wrong," +
Log.i(TAG, "Either key is not encrypted and we were given passphrase, or the passphrase is wrong, " +
"or the key is corrupt.");
e.printStackTrace();
return null;
Expand All @@ -274,8 +299,11 @@ public static String convertToOpenSSHFormat(PublicKey pk, String origNickname) t
String data = "ssh-dss ";
data += String.valueOf(Base64.encode(DSASHA1Verify.get().encodePublicKey(pk)));
return data + " " + nickname;
} else if (pk instanceof ECPublicKey) {
String data = "ecdsa-sha2-nistp521 ";
data += String.valueOf(Base64.encode(ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().encodePublicKey(pk)));
return data + " " + nickname;
}

throw new InvalidKeyException("Unknown key type");
}

Expand Down
19 changes: 15 additions & 4 deletions pubkeyGenerator/src/main/res/layout/act_generatepubkey.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,19 @@
android:id="@+id/key_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkedButton="@+id/rsa"
android:checkedButton="@+id/ecdsa"
android:orientation="horizontal">

<RadioButton
android:id="@+id/ecdsa"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ECDSA" />

<RadioButton
android:id="@+id/rsa"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="30dip"
android:text="RSA" />

<RadioButton
Expand All @@ -111,8 +116,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="number"
android:singleLine="true"
android:text="2048" />
android:singleLine="true" />
</TableRow>

<SeekBar
Expand All @@ -137,6 +141,13 @@
android:enabled="false"
android:text="@string/copy" />

<Button
android:id="@+id/copyPriv"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:enabled="true"
android:text="@string/copy_private_to_clipboard" />

<Button
android:id="@+id/save"
android:layout_width="fill_parent"
Expand Down
Loading

0 comments on commit 1b11cf5

Please sign in to comment.