在HTTPS会话中搜索会话ID,会话密钥
我想找到解决方案来获取SessionID,更重要的是SessionKey。我已经发现这是一个基于Java的解决方案:在HTTPS会话中搜索会话ID,会话密钥
http://jsslkeylog.sourceforge.net
它是使用下面的类来记录RSA-SessionKey:
/**
* Transformer to transform <tt>RSAClientKeyExchange</tt> and
* <tt>PreMasterSecret</tt> classes to log <tt>RSA</tt> values.
*/
public class RSAClientKeyExchangeTransformer extends AbstractTransformer {
public RSAClientKeyExchangeTransformer(String className) {
super(className, "<init>");
}
@Override
protected void visitEndOfMethod(MethodVisitor mv, String desc) {
String preMasterType = "Ljavax/crypto/SecretKey;";
if (className.endsWith("/PreMasterSecret")) {
preMasterType = "[B";
}
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className, "encrypted", "[B");
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className, "preMaster", preMasterType);
mv.visitMethodInsn(INVOKESTATIC, className, "$LogWriter$logRSA", "([B" + preMasterType + ")V");
}
}
在我的Android应用程序,我现在用的是DefaultHttpClient( org.apache.http.impl.client)建立HTTPS连接。对于这个连接,我试图找到SessionKey。如果有可能通过使用android/java方法读出密钥,是否有人有一个想法?如果没有,是否有人知道密钥生成的实现?
我不认为这可以通过公共API完成。您可以获取会话ID,但没有公共接口来获取密钥。
但是,我能够使用反射和本机代码的组合来访问底层的OpenSSL struct,它包含会话ID和主密钥。所以这是可能的,但它不是安全的,因为隐藏的成员和图书馆不保证保持不变。实际上,它看起来像OpenSSL主分支上的结构布局已更改,所以下面的解析代码将需要更新,如果/当它被拉入Android。
我使用URL.openConnection()
而不是DefaultHttpClient
来制作HTTPS连接,因为后者现在已被弃用。下面是调用URL.openConnection()
和(这里没有什么有趣的)替换默认SSLSocketFactory
类:
public class MyConnection implements Runnable {
@Override
public void run() {
try {
// Create the connection.
URL url = new URL("https://www.google.com");
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
// Replace the default SSLSocketFactory with our own.
MySSLSocketFactory sslSocketFactory = new MySSLSocketFactory();
urlConnection.setSSLSocketFactory(sslSocketFactory);
// Establish the TLS connection.
int statusCode = urlConnection.getResponseCode();
Log.i("MyConnection", String.format("status %d", statusCode));
// Get SSL details from the captured socket.
sslSocketFactory.getSessionInfo();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
这里是习俗SSLSocketFactory
,其中最神奇的是。它所做的只是将重写的方法转发到真实的SSLSocketFactory
,缓存创建的SSLSocket
实例。还有两种新的(未覆盖的)方法 - 下面进一步显示的本地方法和getSessionInfo()
,它使用SSLSocket
上的反射来获取本机OpenSSL ssl_session_st
指针并解析(并记录)感兴趣的字段。请注意,您可以使用支持的SSLSession.getId()
获得会话ID;它获得了需要偷偷摸摸的关键。
// Use Decorator pattern to capture the SSL socket from the default SSLSocketFactory.
class MySSLSocketFactory extends SSLSocketFactory {
// Load NDK shared library.
static {
System.loadLibrary("my_native_helper");
}
// All overridden methods will be forwarded to the real SSLSocketFactory.
// The only addition is that the SSLSocket returned by createSocket() is
// cached.
SSLSocketFactory realFactory_ = HttpsURLConnection.getDefaultSSLSocketFactory();
SSLSocket s_;
// This native method copies data from a native pointer into a ByteBuffer.
native void readNative(long pointer, ByteBuffer dst);
// Use the cached SSLSocket to access native OpenSSL session data.
void getSessionInfo() throws NoSuchFieldException, IllegalAccessException {
// Get the protected OpenSSL ssl_session_st pointer. Note that this
// is not part of the API and could change across Android versions.
// See https://android.googlesource.com/platform/external/conscrypt/+/lollipop-mr1-dev/src/main/java/org/conscrypt/OpenSSLSessionImpl.java
SSLSession session = s_.getSession();
Field field = session.getClass().getDeclaredField("sslSessionNativePointer");
field.setAccessible(true);
long sessionPointer = field.getLong(session);
// Read as many bytes as we need from the native pointer.
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(104);
byteBuffer.order(ByteOrder.nativeOrder());
readNative(sessionPointer, byteBuffer);
// Parse the OpenSSL ssl_session_st. Note that the layout of this structure
// may change with OpenSSL versions and different compilers/platforms (e.g.
// 32-bit vs. 64-bit).
// See https://github.com/openssl/openssl/blob/OpenSSL_1_0_0-stable/ssl/ssl.h#L451
IntBuffer intBuffer = byteBuffer.asIntBuffer();
Log.i("MyConnection", String.format("SSL version %04x", intBuffer.get(0)));
int master_key_length = intBuffer.get(4);
String master_key = "";
for (int i = 0; i < master_key_length; ++i)
master_key += String.format("%02x", byteBuffer.get(20 + i));
Log.i("MyConnection", String.format("Master key %s", master_key));
int session_id_length = intBuffer.get(17);
String session_id = "";
for (int i = 0; i < session_id_length; ++i)
session_id += String.format("%02x", byteBuffer.get(72 + i));
Log.i("MyConnection", String.format("Session ID %s", session_id));
}
@Override
public String[] getDefaultCipherSuites() {
return realFactory_.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return realFactory_.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
s_ = (SSLSocket)realFactory_.createSocket(s, host, port, autoClose);
return s_;
}
@Override
public Socket createSocket(String host, int port) throws IOException {
s_ = (SSLSocket)realFactory_.createSocket(host, port);
return s_;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
s_ = (SSLSocket)realFactory_.createSocket(host, port, localHost, localPort);
return s_;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
s_ = (SSLSocket)realFactory_.createSocket(host, port);
return s_;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
s_ = (SSLSocket)realFactory_.createSocket(address, port, localAddress, localPort);
return s_;
}
}
最后,这里是本地C代码,它允许从本地指针读取内存到ByteBuffer中。这需要使用Android NDK构建并加载,如MySSLSocketFactory
的顶部所示。
#include <jni.h>
#include <string.h>
JNIEXPORT
void JNICALL Java_com_example_mysocketfactory_MySSLSocketFactory_readNative(
JNIEnv *env, jobject o,
jlong pointer, jobject buffer) {
const char *p = (const char *)pointer;
memcpy(
(*env)->GetDirectBufferAddress(env, buffer),
p,
(*env)->GetDirectBufferCapacity(env, buffer));
}
就是这样。当MyConnection.run()
我的奇巧设备上调用,日志显示:
I/MyConnection﹕ status 200
I/MyConnection﹕ SSL version 0301
I/MyConnection﹕ Master key 81ef39c5f8f7f796a34b307ff453511378fd081d14c37eb2e912fa829edf280e0fa7a499c370fdc156b8499758373d67
I/MyConnection﹕ Session ID b9ee4ae0c7738909430d47e9b0d6d60420d34a17d08181f21996e55a463aa5cf
我确实同DefaultHttpClient
短暂的尝试,但放弃了,当我无法弄清楚如何访问默认SchemeRegistry。我认为可以通过在构建DefaultHttpClient
时指定ClientConnectionManager
来完成,但是我不想追求更进一步的弃用路径。如果您想尝试,那么您可能会使用类似的方法来拦截处理连接的实例SSLSessionImpl
。这个类有一个master_secret
成员,所以不需要本地代码,只有反射(这段代码路径不使用OpenSSL)。
要添加到rhashimoto的答案,这是我想出的。这种方法不需要搞乱JNI(很公平,它利用现有的JNI接口搅乱)。而且,它只适用于OpenSSLSessionImpl
。
它也从获取本地指针开始,但随后调用i2d_SSL_SESSION()
方法来获取ASN.1编码中的会话数据。最后,它从ASN.1数据中提取主密钥。对未来的OpenSSL版本来说,这应该会更有力。
// Returns master secret as byte array, or null if nothing was found.
private static byte[] getMasterSecret(SSLSession sslSession) {
try {
// First get sslSessionNativePointer from sslSession (assume it is a com.android.org.conscrypt.OpenSSLSessionImpl)
Class sslSessionClass = sslSession.getClass();
Field sslSessionNativePointerField = sslSessionClass.getDeclaredField("sslSessionNativePointer");
sslSessionNativePointerField.setAccessible(true);
long sslSessionNativePointer = sslSessionNativePointerField.getLong(sslSession);
// Then get SSL session object, encoded as ASN.1
Class<?> nativeCryptoClass = Class.forName("com.android.org.conscrypt.NativeCrypto");
Method i2d_SSL_SESSION_method = nativeCryptoClass.getMethod("i2d_SSL_SESSION", long.class);
byte[] sslASN1SessionData = (byte[]) i2d_SSL_SESSION_method.invoke(nativeCryptoClass, sslSessionNativePointer);
// Parse the ASN.1 data
ASN1Primitive asn1Primitive = new ASN1InputStream(new ByteArrayInputStream(sslASN1SessionData)).readObject();
// Get the master secret; blindly assume that the first octet string of 48 bytes is the master secret
if (asn1Primitive instanceof ASN1Sequence) {
for (ASN1Encodable item : (ASN1Sequence) asn1Primitive) {
if (item instanceof ASN1OctetString) {
byte[] octets = ((ASN1OctetString) item).getOctets();
if (octets.length == 48) {
return octets;
}
}
}
}
// Hmm, it failed. Dump all data then.
Log.w("TAG", "Did not find master secret in ASN.1 data.");
Log.w("TAG", ASN1Dump.dumpAsString(asn1Primitive, true));
} catch (IllegalAccessException | ClassNotFoundException | InvocationTargetException | NoSuchMethodException | NoSuchFieldException | IOException e) {
Log.w("TAG", "Failed to get master secret", e);
}
return null;
}
这可以帮助我很多。谢谢你的详细解答.... – davidOhara