2015-05-29 126 views
1

我想找到解决方案来获取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方法读出密钥,是否有人有一个想法?如果没有,是否有人知道密钥生成的实现?

回答

2

我不认为这可以通过公共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)。

+0

这可以帮助我很多。谢谢你的详细解答.... – davidOhara

1

要添加到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; 
}