sftp with public key is not working - java

Generated RSA public key using ssh-keygen.
Trying to use to connect remote server through sftp :
JSch jsch = new JSch();
try {
String publicKey = "/home/testuser/.ssh/id_rsa.pub";
jsch.addIdentity(publicKey);
session = jsch.getSession(sftpUsername, sftpHostname, sftpPort);
session.setConfig("PreferredAuthentications", "publickey,keyboard-interactive,password");
} catch (JSchException e) {
logger.error("Unable to obtain session", e);
}
getting below error :
com.jcraft.jsch.JSchException: invalid privatekey: /home/testuser/.ssh/id_rsa.pub
at com.jcraft.jsch.IdentityFile.<init>(IdentityFile.java:261)
at com.jcraft.jsch.IdentityFile.newInstance(IdentityFile.java:135)
at com.jcraft.jsch.IdentityFile.newInstance(IdentityFile.java:130)
at com.jcraft.jsch.JSch.addIdentity(JSch.java:206)
at com.jcraft.jsch.JSch.addIdentity(JSch.java:192)
Any suggestions ?

You have:
jsch.addIdentity(publicKey);
JSch javadoc says:
public void addIdentity(String prvkey)
throws JSchException;
Adds an identity to be used for public-key authentication. Before registering it into identityRepository, it will be deciphered with passphrase.
Parameters:
prvkey - the file name of the private key file. This is also used as the identifying name of the key. The corresponding public key is assumed to be in a file with the same name with suffix .pub.
You have supplied the public key, when JSch wants the private key.
If you think about it, this makes sense. There's nothing secret about a public key. JSch wants a secret, so it can prove who you are.
Your private key is probably in ~/.ssh/id_rsa (without the .pub extension).
You may need to use the two-parameter version of addIdentity, in order to supply a passphrase to decrypt the private key.

Related

Java Diffie hellman initialize ECDHKeyAgreement

I have a Diffie–Hellman security class like this:
public class AESSecurityCap {
private PublicKey publicKey;
KeyAgreement keyAgreement;
byte[] sharedsecret;
AESSecurityCap() {
makeKeyExchangeParams();
}
private void makeKeyExchangeParams() {
KeyPairGenerator kpg = null;
try {
kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(128);
KeyPair kp = kpg.generateKeyPair();
publicKey = kp.getPublic();
keyAgreement = KeyAgreement.getInstance("ECDH");
keyAgreement.init(kp.getPrivate());
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
}
}
public void setReceiverPublicKey(PublicKey publickey) {
try {
keyAgreement.doPhase(publickey, false); // <--- Error on this line
sharedsecret = keyAgreement.generateSecret();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
}
and implemented this class:
public class Node extends AESSecurityCap {
}
Sometimes I need to reinitialize DH keyAgreement:
public class TestMainClass {
public static void main(String[] args) {
Node server = new Node();
Node client = new Node();
server.setReceiverPublicKey(client.getPublicKey());
client.setReceiverPublicKey(server.getPublicKey());
// My problem is this line ,
// Second time result exception
server.setReceiverPublicKey(client.getPublicKey());
}
}
but receive this exception:
Exception in thread "main" java.lang.IllegalStateException: Phase already executed
at jdk.crypto.ec/sun.security.ec.ECDHKeyAgreement.engineDoPhase(ECDHKeyAgreement.java:91)
at java.base/javax.crypto.KeyAgreement.doPhase(KeyAgreement.java:579)
at ir.moke.AESSecurityCap.setReceiverPublicKey(AESSecurityCap.java:37)
at ir.moke.TestMainClass.main(TestMainClass.java:13)
Is there any way to reinitialize ECDH KeyAgreement multiple time?
This is my test case:
Client initialize DH and generate public key.
Client sent public key to server.
Server initialize DH with client key and generate own public key and generate shared secret key.
Server send public key to client.
Client generate shared secret key with server public key.
In this step client and server has public keys and shared secret.
My problem is client disconnected() and KeyAgreement initialized by singleton object and don't reinitialized second time.
Sometimes I need to do this subject.
Please guide me to fix this problem.
The IllegalStateException (Phase already executed) seems to be especially caused by the ECDH-implementation of the SunEC-provider. The exception doesn't occur if an (additional) init is executed immediately before the doPhase. However, this init-call shouldn't be necessary, since after the doPhase-call generateSecret is executed, which should reset the KeyAgreement-instance to the state after the init-call, at least according to the generateSecret-documentation:
This method resets this KeyAgreement object to the state that it was in after the most recent call to one of the init methods...
Possibly it's a bug in the SunEC-provider. If DH is used instead of ECDH (and the SunJCE-provider instead of the SunEC-provider) the behavior is as expected, i.e. repeated doPhase-calls are possible (without additional init-calls). The same applies to ECDH using the BouncyCastle-provider. Therefore, you could take the BouncyCastle-provider instead of the SunEC-provider to run ECDH with your code.
Note: The second parameter (lastPhase) in doPhase should be set to true, otherwise an IllegalStateException (Only two party agreement supported, lastPhase must be true) is generated (at least for ECDH).
EDIT:
The bug is already known and fixed in JDK 12, see JDK-8205476: KeyAgreement#generateSecret is not reset for ECDH based algorithmm.

JSch - How to let user confirm host fingerprint?

In an Android app, I am attempting to connect to an SSH server using the JSch library. The remote server is specified by the user, so I don't know the remote fingerprint in advance. At the same time I don't want to set StrictHostKeyChecking to no as I see in so many examples.
I'd like to get the remote server fingerprint, show it to the user for acceptance. Is this possible either with JSch or regular Java, perhaps with sockets?
Here's an example you can try, just paste it in the onCreate of an Android activity:
new Thread(new Runnable() {
#Override
public void run() {
com.jcraft.jsch.Session session;
JSch jsch;
try {
jsch = new JSch();
jsch.setLogger(new MyLogger());
session = jsch.getSession("git", "github.com", 22);
session.setPassword("hunter2");
Properties prop = new Properties();
prop.put("StrictHostKeyChecking", "yes");
session.setConfig(prop);
//**Get a host key and show it to the user**
session.connect(); // reject HostKey: github.com
}
catch (Exception e){
LOG.error("Could not JSCH", e);
}
}
}).start();
OK I've found a way to do this. It may not be the best way but it is a way. Using the UserInfo.promptYesNo required looping at the expense of CPU while waiting for user response or with the overhead of an Executor/FutureTask/BlockingQueue. Instead the async thread which executes the connection (since network tasks cannot occur on UI thread) is more conducive to doing this twice - once to 'break' and get the user to accept, second to succeed. I guess this is the 'Android way'. For this, the hostkey needs storing somewhere. Suppose I store it in Android's PreferenceManager, then to start with grab the key from there, defaulting to empty if not available
String keystring = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("target_hostkey","");
if(!Strings.isNullOrEmpty(keystring)){
byte[] key = Base64.decode ( keystring, Base64.DEFAULT );
jsch.getHostKeyRepository().add(new HostKey("github.com", key ), null);
}
Next, proceed as usual to connect to the server
session = jsch.getSession("git", "github.com", 22);
session.setPassword("hunter2");
Properties prop = new Properties();
prop.put("StrictHostKeyChecking", "yes");
session.setConfig(prop);
session.connect();
But this time, catch the JSchException. In there, the session has a HostKey available.
catch(final JSchException jex){
LOG.debug(session.getHostKey().getKey());
final com.jcraft.jsch.Session finalSession = session;
runOnUiThread(new Runnable() {
#Override
public void run() {
new MaterialDialog.Builder(MyActivity.this)
.title("Accept this host with fingerprint?")
.negativeText(R.string.cancel)
.positiveText(R.string.ok)
.content(finalSession.getHostKey().getFingerPrint(jsch))
.onPositive(new MaterialDialog.SingleButtonCallback() {
#Override
public void onClick(#NonNull MaterialDialog dialog, #NonNull DialogAction which) {
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).edit().putString("target_hostkey", finalSession.getHostKey().getKey()).apply();
}
}).show();
}
});
}
After this, it's a matter of re-invoking the Thread or AsyncTask but this time the hostkey is added to the hostkey repository for JSch.
Two possibilities:
When StrictHostKeyChecking is set to ask, JSch calls UserInfo.promptYesNo with a confirmation prompt. Implement the UserInfo interface to display the confirmation to the user. Disadvantage is that you cannot customize the message in any way (of course, unless you try to parse it, relying on a hard-coded template).
The message is like:
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that the -key_type- host key has just been changed.
The fingerprint for the -key_type- key sent by the remote host is
-key_fprint-
Please contact your system administrator.
Add correct host key in -file- to get rid of this message.
For an example implementation, see the official JSch KnownHosts.java example.
Even before the above, JSch calls HostKeyRepository.check, passing it hostname and the key.
You can implement that interface/method, to do any prompt you like.
Check Session.checkHost implementation.

Java JSchException: Auth cancel

I am currently seeing this issue when attempting to ssh into a box from using JSch. I have tested the connection using Cygwin and it connects seamlessly. I have generated the keypair and placed the Public key in authorized_keys file on the remote server.
Below is an extract from the logs
INFO: Next authentication method: publickey
INFO: Authentications that can continue: keyboard-interactive,password
INFO: Next authentication method: keyboard-interactive
INFO: Authentications that can continue: password
INFO: Next authentication method: password
INFO: Disconnecting from xx.xx.xx.xx port 22
com.jcraft.jsch.JSchException: Auth cancel
Code used to established connection
Properties config = new Properties();
config.put("cipher",",aes256-cbc");
config.put("mac.c2s", "hmac-sha2-256");
config.put("KEXs", "diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group-exchange-sha256");
config.put("StrictHostKeyChecking", "no");
Session session = jsch.getSession(username,host,port);
session.setPassword(password);
session.setUserInfo(ui);
session.setConfig(config);
session.getPort();
session.connect();
session.setPortForwardingL(tunnelLocalPort,tunnelRemoteHost,tunnelRemotePort);
Here is the code for the UserInfo ui
String password = null;
#Override
public String getPassphrase() {
return null;
}
#Override
public String getPassword() {
return password;
}
public void setPassword(String passwd) {
password = passwd;
}
#Override
public boolean promptPassphrase(String message) {
return false;
}
#Override
public boolean promptPassword(String message) {
return false;
}
#Override
public boolean promptYesNo(String message) {
return false;
}
It looks like jsch isn't trying to use a key file, because your code doesn't tell jsch what key file to use. You need to call Jsch.addIdentity() to add one or more key files to the session:
jsch.addIdentity("C:\users\jdoe\.ssh\id_rsa");
or
String passphrase = ...;
jsch.addIdentity("C:\users\jdoe\.ssh\id_rsa",
passphrase.getBytes());
There are other varieties of the addIdentity() function, if you want to supply the information in some other format.
The "Auth cancel" is thrown when the authentication implementation throws JSchAuthCancelException. What in turn usually happens when the UserInfo implementation return false from one of its methods.
Your code does not show what is the ui. So I cannot provide more information until you show us more code.
Also you write about key pair, yet your code does not show any use of a key. You instead set a password.
For private key authentication with JSch see for example:
Can we use JSch for SSH key-based communication?

Chat Application - Cryptography

I have created a chat application and to finish it I have to implement some Cryptography algorithm to secure the messages between server - client.
My implementation is:
1.Client creates kaypair (public and private key) and sends public key to server.
2.Server gets public key and creates symmetric key encrypted with the public key.
3.Server sends the encrypted key to Client.
4.Client unlocks symmetric key with private key.
5.Client and Server communicate with Symmetric key.
This part of the code is where the server gets the public key and sends the symmetric key encrypted
else if(msg.type.equals("pubKey")){
pubKey = msg.content; //get public key
String key = Arrays.toString(crypt.geteKey());
clients[findClient(ID)].send(new Message("symmKey", "SERVER", key, msg.sender));//! //send symmetric key encrypted with public key
}
Key encryption method:
public void keyEncryption() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
eKey = cipher.doFinal(key.getBlowfishKeyBytes()); //symmetric key encrypted with public key
//System.out.println("2. cipherText= " + bytesToHex(symmKey));
}
How I get encrypted symmetric key from server:
else if(msg.type.equals("symmKey")){
symmKey = (String) msg.content; //get encrypted symmetric key (must unlock with private key)
}
The Server Message class: (Client Message class has "object content" instead of String)
package com.socket;
import java.io.Serializable;
public class Message implements Serializable{
private static final long serialVersionUID = 1L;
public String type, sender,content, recipient;
public Message(String type, String sender, String content, String recipient){
this.type = type; this.sender = sender; this.content = content; this.recipient = recipient;
}
#Override
public String toString(){
return "{type='"+type+"', sender='"+sender+"', content='"+content+"', recipient='"+recipient+"'}";
}
}
Client GUI where I send the key to the Server:
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
//KeyPair
try {
keyPair = new Keypair();
} catch (NoSuchAlgorithmException ex) {
jTextArea1.append("Security Error! You are not safe!");
}
Object pubKey = keyPair.getKeyPair().getPublic();
username = jTextField3.getText();
password = jPasswordField1.getText();
if (!username.isEmpty() && !password.isEmpty()) {
client.send(new Message("login", username, password, "SERVER"));
client.send(new Message("pubKey",username, pubKey, "SERVER")); //send Public key to Server
}
}
Error I Get on Server:
Database exception : userExists()
53846 ERROR reading: cannot assign instance of sun.security.rsa.RSAPublicKeyImpl to field com.socket.Message.content of type java.lang.String in instance of com.socket.Message
I have implemented steps 1-3 but I get this exception... If anyone has any idea how to deal with this issue, please help me.
(I will provide any additional code if required.)
Thank you.
msg.content is instance of String and you trying assign it to sun.security.rsa.RSAPublicKeyImpl here:
pubKey = msg.content;
Just on a note, your implementation looks susceptible to a man-in-the-middle attack. If we call your client and server Alice and Bob. I'm Mallory - a malicious eavesdropper.
Alice creates public-private key pair and sends public key to Bob.
Mallory intercepts this, keeps Alice's public key for later and sends his own public key on to Bob.
Bob receives Mallory's public key, thinking it belongs to Alice, generates a session key, encrypts it and sends back to Mallory.
Mallory decrypts the session key, reencrypts using Alice's public key and returns it to her.
Alice decrypts using her private key and happily goes about sending encrypted messages to Bob, not realising that Mallory has intercepted the session key.
Mallory now listens in on their conversation and sells Alice's mums recipe for chocolate cake to the highest bidder. Alice blames Bob, Bob blames Alice etc.
You will need to introduce signing to your protocol to help to ensure authenticity of your protocol: Alice and Bob need to be certain that that the communications are original and have not been tampered with. I'm on my phone but will see if I can find a decent link later for you.

Using Keys with JGit to Access a Git Repository Securely

I'm using JGit to access a remote Git repo, and I need to use SSH for it. JGit uses JSch to provide secure access. However, I'm not sure how to set the key file and the knows hosts file for JGit. What I have tried is as follows.
Created a custom configuration of the SshSessionFactory, using by subclassing JSchConfigSessionFactory:
public class CustomJschConfigSessionFactory extends JschConfigSessionFactory {
#Override
protected void configure(OpenSshConfig.Host host, Session session) {
session.setConfig("StrictHostKeyChecking", "yes");
}
}
In the class which I access the remote Git repo, did the following:
CustomJschConfigSessionFactory jschConfigSessionFactory = new CustomJschConfigSessionFactory();
JSch jsch = new JSch();
try {
jsch.addIdentity(".ssh/id_rsa");
jsch.setKnownHosts(".ssh/known_hosts");
} catch (JSchException e) {
e.printStackTrace();
}
SshSessionFactory.setInstance(jschConfigSessionFactory);
I can't figure out how to associate this JSch object with JGit so that it can successfully connect to the remote repository. When I try to clone it with JGit, I get the following exception:
org.eclipse.jgit.api.errors.TransportException: git#git.test.com:abc.org/test_repo.git: reject HostKey: git.test.com
at org.eclipse.jgit.api.FetchCommand.call(FetchCommand.java:137)
at org.eclipse.jgit.api.CloneCommand.fetch(CloneCommand.java:178)
at org.eclipse.jgit.api.CloneCommand.call(CloneCommand.java:125)
at GitTest.cloneRepo(GitTest.java:109)
at GitTest.main(GitTest.java:223)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: org.eclipse.jgit.errors.TransportException: git#git.test.com:abc.org/test_repo.git: reject HostKey: git.test.com
at org.eclipse.jgit.transport.JschConfigSessionFactory.getSession(JschConfigSessionFactory.java:142)
at org.eclipse.jgit.transport.SshTransport.getSession(SshTransport.java:121)
at org.eclipse.jgit.transport.TransportGitSsh$SshFetchConnection.<init>(TransportGitSsh.java:248)
at org.eclipse.jgit.transport.TransportGitSsh.openFetch(TransportGitSsh.java:147)
at org.eclipse.jgit.transport.FetchProcess.executeImp(FetchProcess.java:136)
at org.eclipse.jgit.transport.FetchProcess.execute(FetchProcess.java:122)
at org.eclipse.jgit.transport.Transport.fetch(Transport.java:1104)
at org.eclipse.jgit.api.FetchCommand.call(FetchCommand.java:128)
... 9 more
Caused by: com.jcraft.jsch.JSchException: reject HostKey: git.test.com
at com.jcraft.jsch.Session.checkHost(Session.java:748)
at com.jcraft.jsch.Session.connect(Session.java:321)
at org.eclipse.jgit.transport.JschConfigSessionFactory.getSession(JschConfigSessionFactory.java:116)
... 16 more
I have added the git.test.com entry to my /etc/hosts file. I have used the same code to access a git repo with a http url, so the code it working fine. It's the key handling part that is failing. Any idea on how to handle this?
You need to override the getJSch method in your custom factory class:
class CustomConfigSessionFactory extends JschConfigSessionFactory
{
#Override
protected JSch getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException {
JSch jsch = super.getJSch(hc, fs);
jsch.removeAllIdentity();
jsch.addIdentity( "/path/to/private/key" );
return jsch;
}
}
Calling jsch.removeAllIdentity is important; it doesn't seem to work without it.
A caveat: I wrote the above in Scala, and then translated it over to Java, so it might not be quite right. The original Scala is as follows:
class CustomConfigSessionFactory extends JschConfigSessionFactory
{
override protected def getJSch( hc : OpenSshConfig.Host, fs : FS ) : JSch =
{
val jsch = super.getJSch(hc, fs)
jsch.removeAllIdentity()
jsch.addIdentity( "/path/to/private/key" )
jsch
}
}
Jsch sesems to not like a known_hosts file in the hashed format-- it must conform to the format produced by:
ssh-keyscan -t rsa hostname >> ~/.ssh/known_hosts
e.g.
<hostname> ssh-rsa <longstring/longstring>
not:
|1|<hashed hostname>= ecdsa-sha2-nistp256 <hashed fingerprint>=
Managed to find the issue. The public key in the server side had a different name other than the usual id_rsa.pub, while the private key on my side was id_rsa. JSch expects by default the public key to have the same name as the private key plus the .pub suffix. Using a key pair with a common name (ex.: private = key_1 and public = key_1.pub) solves the issue.

Categories