【问题标题】:Android SSHJ exception upon connect() - "KeyFactory ECDSA implementation not found"连接()时的 Android SSHJ 异常-“找不到 KeyFactory ECDSA 实现”
【发布时间】:2014-12-26 12:25:43
【问题描述】:

我正在尝试从我的 Android 应用程序打开一个 SSH 客户端会话。尝试连接到本地网络上的设备(Raspberry Pi)。我正在使用 SSHJ 库版本 0.10.0。它在ssh.connect() 调用上失败,TransportException 最终由NoSuchAlgorithmException 引起。请参阅下面的异常树。

SSHClient ssh = new SSHClient(new AndroidConfig());
Session session = null;

try {    
    //ssh.loadKnownHosts();

    // Exception thrown on this line
    ssh.connect("192.168.1.109", 22);

    // Doesn't reach below
    ssh.authPassword("user", "password");
    session = ssh.startSession();
}
catch (net.schmizz.sshj.transport.TransportException ex) {
    ;
}

异常树:

net.schmizz.sshj.transport.TransportException
 net.schmizz.sshj.common.SSHException
  net.schmizz.sshj.common.SSHRuntimeException
   java.security.GeneralSecurityException: java.security.NoSuchAlgorithmException: KeyFactory ECDSA implementation not found
    java.security.NoSuchAlgorithmException: KeyFactory ECDSA implementation not found

其他系统信息:

SSHJ library   : v0.10.0
Android device : Galaxy Note 3 running Android 4.4.2

我使用 Android Studio 中的 maven 依赖支持引入了 SSHJ JAR,除了 SSHJ v0.10.0 jar 之外,它还引入了以下三个库...

bouncy castle...
  bcpkix-jdk15on-1.50.jar
  bcprov-jdk15on-1.50.jar
logging....
  slf4j-api-1.7.7.jar

不知道从哪里开始这个例外......任何建议表示赞赏!谢谢。

更新:2014 年 10 月 31 日

按照LeeDavidPainter 的建议,我包含了 SpongyCastle 1.51.0 JAR 并在顶部添加了这一行:

Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);

我现在在同一行遇到不同的异常:

net.schmizz.sshj.transport.TransportException
 net.schmizz.sshj.common.SSHException
  net.schmizz.sshj.common.SSHRuntimeException
   java.security.GeneralSecurityException: java.security.spec.InvalidKeySpecException: key spec not recognised
    java.security.spec.InvalidKeySpecException: key spec not recognised

另请注意,我也尝试了以下行,结果相同:

Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());

我的手机上有另一个应用程序,它基本上正在做我想要实现的目标 - 它称为 RaspberryPiController - 它通过 SSH 连接到您的 RPi,并使用用户名和密码进行身份验证。这工作正常,所以它似乎不是网络问题。

【问题讨论】:

    标签: java android ssh raspberry-pi sshj


    【解决方案1】:

    Android 附带一个精简版的 BouncyCastle,其中不包含 ECDSA 算法。因此,即使您在类路径中包含完整版本,也会选择并使用 Android 运行时版本。

    您可能想查看为解决此问题而创建的http://rtyley.github.io/spongycastle/,它是 Bouncycastle 的重新打包版本,可以作为单独的 JCE 提供程序安装在 Android 中。只需在尝试连接 SSHJ 之前将其安装为默认 JCE 提供程序(未经测试)。

    Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
    

    【讨论】:

    • 感谢您的建议 - 我试了一下,现在遇到了一个不同的异常 - java.security.spec.InvalidKeySpecException:无法识别密钥规范。它发生在同一个地方 - 在 ssh.connect() 调用上。我已经用详细信息更新了上面的问题。
    • 我有同样的问题,引入这个新的 JCE 和上面的评论有同样的问题......
    • 在 Android 4.4.4 上,我必须调用 Security.removeProvider("BC") 之前调用 Security.insertProviderAt(new BouncyCastleProvider(), Security.getProviders().length+1)。否则,BouncyCastleProvider 永远不会被添加到提供者列表中。
    • 是的,它有效,甚至在我试图这样做的android上也没有。
    【解决方案2】:

    首先在 app/build.gradle 文件中添加这个 BouncyCastle 库:

    implementation 'org.bouncycastle:bcpkix-jdk15on:1.64'
    

    然后在您的活动文件中,添加一个静态块以删除 Android 中使用我们版本的默认 BouncyCastle 提供程序:

        static {
            Security.removeProvider("BC");//first remove default os provider
            Security.insertProviderAt(new BouncyCastleProvider(), 1);//add new provider
        }
    

    这将解决算法实现未找到的问题。

    【讨论】:

    • 这是有效的解决方案。但是,如果您使用任何其他提供程序也用于不同的操作,请使用以下代码。对于前。 Android Keystore Security.removeProvider("BC");//first remove default os provider Security.addProvider(new BouncyCastleProvider());//add new provider 它将在列表末尾添加新的提供者。
    【解决方案3】:

    在此处降级到 sshj 0.9.0:http://mvnrepository.com/artifact/net.schmizz/sshj/0.9.0

    这个问题似乎是在 0.10.x 中引入的。另外,我尝试过其他 JCE 提供程序,但也遇到了同样的问题。

    【讨论】:

      【解决方案4】:

      Jsch 很可能有效,因为它不支持 SSH AFAIK 的椭圆曲线算法。如果您不需要椭圆曲线算法,那么这就是您的答案。

      【讨论】:

        【解决方案5】:

        在 SSHJ 中无法解决此问题,因此决定尝试JSch,它提供了相同的功能。它也可以作为 maven repo 使用——我使用了jsch version 0.1.51 ('com.jcraft:jsch:0.1.51')。

        这是第一次使用此代码片段;

        import com.jcraft.jsch.ChannelExec;
        import com.jcraft.jsch.JSch;
        import com.jcraft.jsch.JSchException;
        
        import java.io.ByteArrayOutputStream;
        import java.util.Properties;
        
        JSch jsch = new JSch();
        com.jcraft.jsch.Session session = null;
        String result = "";
        
        try {    
          session = jsch.getSession("user", "192.168.1.109", 22);
          session.setPassword("password");
        
          // Avoid asking for key confirmation
          Properties prop = new Properties();
          prop.put("StrictHostKeyChecking", "no");
          session.setConfig(prop);
          session.connect();
        
          // SSH Channel
          ChannelExec channel = (ChannelExec)session.openChannel("exec");
          ByteArrayOutputStream stream = new ByteArrayOutputStream();
          channel.setOutputStream(stream);
        
          // Execute command
          channel.setCommand("ls -ltr");
          channel.connect(1000);
          java.lang.Thread.sleep(500);   // this kludge seemed to be required.
          channel.disconnect();
        
          result = stream.toString();
        }
        catch (JSchException ex) {
          String s = ex.toString();
          System.out.println(s);
        }
        catch (InterruptedException ex) {
          String s = ex.toString();
          System.out.println(s);
        }
        finally {
          if (session != null)
            session.disconnect();
        }
        

        与 SSHJ 相比,使用它时感觉像是一个更强大的实现——或者这种印象可能是由于他们选择了相当保守的超时。例如,如果目标设备已关闭,默认情况下 session.connect() 调用将在放弃前继续尝试连接大约 20 秒。

        【讨论】:

        • 如果没有用户名或密码或任何身份验证ssh -p 4000 -R test:1777:localhost:1337 test.mydomain.com,如何执行以下命令,如下所示
        【解决方案6】:

        基于 LeeDavidPainter 的解决方案,

        /**
         * Creates a new SSH client stub
         */
        public SSH(final String host, final int port)
        {
            SecurityUtils.setSecurityProvider(SecurityUtils.BOUNCY_CASTLE); //<-- Here
            Security.insertProviderAt(new BouncyCastleProvider(), 1); //<-- Here
            this.ssh.addHostKeyVerifier(new PromiscuousVerifier());
            this.shell = new SSHShellSession();
            this.ssh = new SSHClient();
            this.connected = false;
            this.initiated = false;
            this.host = host;
            this.port = port;
        }
        

        上面的两个注释区域 //

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-09-28
          • 1970-01-01
          • 1970-01-01
          • 2012-11-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-01-30
          相关资源
          最近更新 更多