2015-05-29 169 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; 
}