修改sshd_config,
PasswordAuthentication no
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile %h/.ssh/authorized_keys
KeyRegenerationInterval 3600
ServerKeyBits 1024
在服务器上,要登录的用户的主目录下,mkdir .ssh,chmod 700 .ssh,.ssh下创建文件authorized_keys,chmod 600 authorized_keys
SecureCRT创建密钥对,这里对于passphrase,为空,不使用单独的密文来登录。同时选择openssh格式。将.pub的内容复制到authorized_keys。SecureCRT设置好后可直接登录。
这样操作一下就清楚了,客户端创建密钥对,服务器得到.pub(在登录时会进行比较)。
在前面分析的时候也看到有处代码会createkey...什么的。
shell代码修改,
par.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePublicKey;运行,没有任何反应。
发现代码里没有错误信息获取,添加
connect(con,&QSsh::SshConnection::error,
[this](QSsh::SshError errMsg){
qDebug("err code: %d", errMsg);
qDebug() << "err: "<<con->errorString();});可提示
err code: 5
err: "No private key file given."
回过去看之前的分析,可以看到在connectToHost会判断
void SshConnectionPrivate::connectToHost()
{
。。。
try {
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePublicKey)
createPrivateKey();
} catch (const SshClientException &ex) {
m_error = ex.error;
m_errorString = ex.errorString;
emit error(m_error);
return;
}
。。。
在初始化参数时需要设置 privateKeyFile 私钥文件,设置一下,开始时用SecureCRT了,就用它。}void SshConnectionPrivate::createPrivateKey(){if (m_connParams.privateKeyFile.isEmpty())throw SshClientException(SshKeyFileError, tr("No private key file given."));QFile keyFile(m_connParams.privateKeyFile);if (!keyFile.open(QIODevice::ReadOnly)) {throw SshClientException(SshKeyFileError,tr("Private key file error: %1").arg(keyFile.errorString()));}
m_sendFacility.createAuthenticationKey(keyFile.readAll());}
却打印出
err: "Decoding of private key file failed: Format not understood."
void SshEncryptionFacility::createAuthenticationKey(const QByteArray &privKeyFileContents)
{
if (privKeyFileContents == m_cachedPrivKeyContents)
return;
#ifdef CREATOR_SSH_DEBUG
qDebug("%s: Key not cached, reading", Q_FUNC_INFO);
#endif
QList<BigInt> pubKeyParams;
QList<BigInt> allKeyParams;
QString error1;
QString error2;
if (!createAuthenticationKeyFromPKCS8(privKeyFileContents, pubKeyParams, allKeyParams, error1)
&& !createAuthenticationKeyFromOpenSSL(privKeyFileContents, pubKeyParams, allKeyParams,
error2)) {
#ifdef CREATOR_SSH_DEBUG
qDebug("%s: %s\n\t%s\n", Q_FUNC_INFO, qPrintable(error1), qPrintable(error2));
#endif
throw SshClientException(SshKeyFileError, SSH_TR("Decoding of private key file failed: "
"Format not understood."));
}
foreach (const BigInt &b, allKeyParams) {
if (b.is_zero()) {
throw SshClientException(SshKeyFileError,
SSH_TR("Decoding of private key file failed: Invalid zero parameter."));
}
}
m_authPubKeyBlob = AbstractSshPacket::encodeString(m_authKeyAlgoName);
foreach (const BigInt &b, pubKeyParams)
m_authPubKeyBlob += AbstractSshPacket::encodeMpInt(b);
m_cachedPrivKeyContents = privKeyFileContents;
}
可以看到PKCS8、OpenSSL,先搜索一下openssl和pkcs8之间的关系。
openssl pem格式,某些语言需要pkcs8格式,将pem转为pkcs8的,pem格式是大众格式,SecureCRT生成的openssh格式就是pem格式。
我选择的是openssh的,那用SecureCRT使用标准的去生成,再试下。还是一样,标准的也不是这两个?那就先看下这两个函数。
bool SshEncryptionFacility::createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents,
QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams, QString &error)
{
try {
Pipe pipe;
pipe.process_msg(convertByteArray(privKeyFileContents), privKeyFileContents.size());
Private_Key * const key = PKCS8::load_key(pipe, m_rng, SshKeyPasswordRetriever());
if (DSA_PrivateKey * const dsaKey = dynamic_cast<DSA_PrivateKey *>(key)) {
m_authKeyAlgoName = SshCapabilities::PubKeyDss;
m_authKey.reset(dsaKey);
pubKeyParams << dsaKey->group_p() << dsaKey->group_q()
<< dsaKey->group_g() << dsaKey->get_y();
allKeyParams << pubKeyParams << dsaKey->get_x();
} else if (RSA_PrivateKey * const rsaKey = dynamic_cast<RSA_PrivateKey *>(key)) {
m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
m_authKey.reset(rsaKey);
pubKeyParams << rsaKey->get_e() << rsaKey->get_n();
allKeyParams << pubKeyParams << rsaKey->get_p() << rsaKey->get_q()
<< rsaKey->get_d();
} else {
qWarning("%s: Unexpected code flow, expected success or exception.", Q_FUNC_INFO);
return false;
}
} catch (const Botan::Exception &ex) {
error = QLatin1String(ex.what());
return false;
} catch (const Botan::Decoding_Error &ex) {
error = QLatin1String(ex.what());
return false;
}
return true;
}
bool SshEncryptionFacility::createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams, QString &error)
{
try {
bool syntaxOk = true;
QList<QByteArray> lines = privKeyFileContents.split('\n');
while (lines.last().isEmpty())
lines.removeLast();
if (lines.count() < 3) {
syntaxOk = false;
} else if (lines.first() == PrivKeyFileStartLineRsa) {
if (lines.last() != PrivKeyFileEndLineRsa)
syntaxOk = false;
else
m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
} else if (lines.first() == PrivKeyFileStartLineDsa) {
if (lines.last() != PrivKeyFileEndLineDsa)
syntaxOk = false;
else
m_authKeyAlgoName = SshCapabilities::PubKeyDss;
} else {
syntaxOk = false;
}
if (!syntaxOk) {
error = SSH_TR("Unexpected format.");
return false;
}
QByteArray privateKeyBlob;
for (int i = 1; i < lines.size() - 1; ++i)
privateKeyBlob += lines.at(i);
privateKeyBlob = QByteArray::fromBase64(privateKeyBlob);
BER_Decoder decoder(convertByteArray(privateKeyBlob), privateKeyBlob.size());
BER_Decoder sequence = decoder.start_cons(SEQUENCE);
size_t version;
sequence.decode (version);
if (version != 0) {
error = SSH_TR("Key encoding has version %1, expected 0.").arg(version);
return false;
}
if (m_authKeyAlgoName == SshCapabilities::PubKeyDss) {
BigInt p, q, g, y, x;
sequence.decode (p).decode (q).decode (g).decode (y).decode (x);
DSA_PrivateKey * const dsaKey = new DSA_PrivateKey(m_rng, DL_Group(p, q, g), x);
m_authKey.reset(dsaKey);
pubKeyParams << p << q << g << y;
allKeyParams << pubKeyParams << x;
} else {
BigInt p, q, e, d, n;
sequence.decode(n).decode(e).decode(d).decode(p).decode(q);
RSA_PrivateKey * const rsaKey = new RSA_PrivateKey(m_rng, p, q, e, d, n);
m_authKey.reset(rsaKey);
pubKeyParams << e << n;
allKeyParams << pubKeyParams << p << q << d;
}
sequence.discard_remaining();
sequence.verify_end();
} catch (const Botan::Exception &ex) {
error = QLatin1String(ex.what());
return false;
} catch (const Botan::Decoding_Error &ex) {
error = QLatin1String(ex.what());
return false;
}
return true;
}网上搜了下,好像有好几种数据格式,但推荐openssh2的。既然 SecureCRT可以用,那就只是qt这边解析问题了。大不了自己写一个。
看函数要得到什么东西,哎,看到openssl,和自己生成的格式比较近,那就基于它来修改。自己的私钥格式:
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDEBXrFsWakn2timFhS1t5Pv7lWN3wYmjjCc0H+y8wb0AsnwvUX
fZDxsDSD7RIUdRxN5ok7sQOJNTdOd6JEfwOFU0GzVpqe3TuOmD9plAYZFaAJJVO3
FsEnGRn0mgMAiSI3WkIcNWEJZnB4i3z7tW4lOHbt6UsqFaRaM8N6EvncowIDAQAB
AoGAPqApi0OUMYAlRMi7Xyv6tqvgQKVVZl7PR4CfUxI1UaV0Cu4Ec87QIgdTn9p7
7kFEG7dNnN5+7LUtyEn8c7nIm54m0SG4Ae84TP0emVnjcIQsKWged8Rn9UF7jt93
slBVd679liAEdSG8IOz7RSdfEM1gwR95iKI27T5Fp4VEB4ECQQD2V4gILpDERC9L
tT4JPjrXz8prflQCJuMtTkVLOBNAefzgWozou5WflRL4SNNx9WqiD6ltX+KJywrw
xeyhnZRhAkEAy7Tk/zqXMPn2NNmDyZ2br0rPMRGRT4m9Nyy8KFhqXH3HDT5hFnm3
SiFsKe44OT9cEhfDoztQNK3D43cpGa1PgwJASClwAeWSJsxKhoT7PT3rgtit1TtW
ZKL7nYP2LIiqqxKRDy+3Y13AANVsjyrWNeXMbNjlqtKRzAeclteD7/v3QQJBALk9
inA+C3bSF33/vsP8urIwyTDJ1OYLTzF5ULEvlaj+B6CXdRpHHNIgioemxSHp6APb
8F/jdBAzY8rZdVqQK5kCQGGkFCjuNFbellXPeaLJ+cp4tqhR7d4r4qmL3F7J/e6e
3QgyWHAE8KQ62hlY6iInYnpwvpwdKdpHL85KIv1hpIs=
-----END RSA PRIVATE KEY-----
应该要注意:1. 头 2. 尾
而代码中正好有
static const QByteArray PrivKeyFileStartLineRsa;
static const QByteArray PrivKeyFileStartLineDsa;
static const QByteArray PrivKeyFileEndLineRsa;
static const QByteArray PrivKeyFileEndLineDsa;只要修改对就可以了。
PrivKeyFileStartLineRsa("-----BEGIN RSA PRIVATE KEY-----");
PrivKeyFileStartLineDsa("-----BEGIN DSA PRIVATE KEY-----");
PrivKeyFileEndLineRsa("-----END RSA PRIVATE KEY-----");
PrivKeyFileEndLineDsa("-----END DSA PRIVATE KEY-----");-----BEGIN RSA PRIVATE KEY-----
-----BEGIN RSA PRIVATE KEY-----
复制下来一比较,哎,一模一样的啊。那需要调试QSsh了,看看哪里出错,返回false
if (lines.count() < 3) {
syntaxOk = false;
} else if (lines.first() == PrivKeyFileStartLineRsa) {
qDebug("+++ rsa file +++");
if (lines.last() != PrivKeyFileEndLineRsa) {
qDebug("+++ no rsa end +++");
syntaxOk = false;
} else
m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
} else if (lines.first() == PrivKeyFileStartLineDsa) {
qDebug("+++ dsa file +++");
if (lines.last() != PrivKeyFileEndLineDsa) {
qDebug("+++ no dsa end +++");
syntaxOk = false;
} else
m_authKeyAlgoName = SshCapabilities::PubKeyDss;
} else {
qDebug("+++ other +++");
syntaxOk = false;
}
if (!syntaxOk) {
qDebug("syntaxOk = false");
error = SSH_TR("Unexpected format.");
return false;
}
+++ file lines 15 +++
+++ other +++
syntaxOk = false
竟然提示other,那就打印一下lines.first()是什么东西
lines.first: -----BEGIN RSA PRIVATE KEY-----
, len 32
StartLineRsa: -----BEGIN RSA PRIVATE KEY-----, len 31
果然是'\n'的问题。这样可以说是QSsh的一个BUG了。
看源码有sshkeycreationdialog.cpp,待会用这个去生成看下,估计也是一样的吧。哎,不对呀,这是从QTCreator提取出来的呀,难道被修改过?
先加'\n'看下
PrivKeyFileStartLineRsa("-----BEGIN RSA PRIVATE KEY-----\n");
PrivKeyFileStartLineDsa("-----BEGIN DSA PRIVATE KEY-----\n");
PrivKeyFileEndLineRsa("-----END RSA PRIVATE KEY-----\n");
PrivKeyFileEndLineDsa("-----END DSA PRIVATE KEY-----\n");
+++ file lines 15 +++
lines.first: -----BEGIN RSA PRIVATE KEY-----
, len 32
StartLineRsa: -----BEGIN RSA PRIVATE KEY-----
, len 32
lines.last: -----END RSA PRIVATE KEY-----
, len 30
EndLineRsa: -----END RSA PRIVATE KEY-----
, len 30
+++ other +++
syntaxOk = false
还是出错,真是对QByteArray的==无语了
QByteArray str = "1234567890\n\n123123\n";
const QByteArray test("1234567890");
QList<QByteArray> lines = str.split('\n');
while (lines.last().isEmpty())
lines.removeLast();
qDebug("+++ file lines %d +++", lines.count());
qDebug("lines.first: %s, len %d", lines.first().constData(), lines.first().length());
qDebug("test: %s, len %d", test.constData(), test.length());
if(lines.first() == test) {
qDebug(" eq ");
} else {
qDebug(" not eq ");
}
+++ file lines 3 +++
lines.first: 1234567890,len 10
test: 1234567890,len 10
eq
看来是对QByteArray的split无语了。为什么这个就不含'\n',前面那个就会有'\n'
后来冷静下来一想\n,应该是windows的\r\n和linux的\n问题。
果然“
在Linux下敲一下回车键,系统加入一个"\n",换行符,ASCII值为10(0xA)
在Winows下敲一下回车键,系统加入一个"\n"和一个"\r",换行符、回车符,ASCII值为10(0xA)、13(0xd)
Qt秉承了Linux的风格,只加入了一个"\n"。
我使用Qt Creator在Winows写了一个串口调试程序,在调试AT指令时有些指令要求“AT+<Enter>”,把我难住了!后经过“toHex()”才发现上述问题。
在对QByteArray 求长度时换行符、回车符都占一个字符长度。如:对“A”+“<Enter>”Linux下2个字符长度,Winows下是3个字符长度。
所以解决方法如下:判断字符串最后一个字符是否“\n”,如果是就再加上一个“\r”。代码如下
QByteArray ba=ui->TextEdit->document()->toPlainText().toAscii(); //取出TextEdit的内容并转换为QByteArray这个问题就简单了,只要将生成的文件用UE转DOS到UNIX就可以了\r\n(0xd 0xa)就会变为\n(0xa)
再测试,
state = 2, remote data size = 41
void QSsh::Internal::SshConnectionPrivate::handleServerId(): incoming data size = 41, incoming data = 'SSH-2.0-OpenSSH_6.9p1 Ubuntu-2ubuntu0.1
'
state = 2, remote data size = 952
state = 2, remote data size = 848
state = 3, remote data size = 52
state = 4, remote data size = 52
err code: 6
err: "Server rejected key."
至少private key文件可以了。回过去看,发觉createPrivateKey()这函数名和功能不一致啊,应该改为
createAuthenticationKey()
接下来继续调试QSsh,打印public key的内容,看生成了什么,和SecureCRT生成的是否一致
竟然是空的。打印,文件读取没问题
代码中看待fromBase64,所以每一行的内容其实是做base64转换。调试了半天,突然看到代码,是我把参数的userName给注释掉了。
这样就简单了,
如果是密码登录
par.port = 22;
par.userName = "ghost";
par.password = "ghost";
par.timeout = 500;
par.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePassword;如果是密钥认证
par.port = 22;
par.userName = "ghost";
par.timeout = 500;
par.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePublicKey;
par.privateKeyFile = path;