TLS Fingerprinting

Identify client and server that are using encrypted connections

TLS Audit Records

Watch a quick demo of creating and exploring the TLSClientHello audit records on the command-line


JA3 is a technique developed by Salesforce, to fingerprint the TLS client and server hellos.

The official python implementation can be found here.

More details can be found in their blog post:

Support for JA3 and JA3S in netcap is implemented via:

The TLSClientHello and TLSServerHello audit records, as well as the DeviceProfiles provide JA3 hashes.

JA3 Details

JA3 gathers the decimal values of the bytes for the following fields: SSL Version, Accepted Ciphers, List of Extensions, Elliptic Curves, and Elliptic Curve Formats.

It then concatenates those values together in order, using a “,” to delimit each field and a “-” to delimit each value in each field.

The field order is as follows:




If there are no SSL Extensions in the Client Hello, the fields are left empty.



These strings are then MD5 hashed to produce an easily consumable and shareable 32 character fingerprint.

This is the JA3 SSL Client Fingerprint.

JA3 is a much more effective way to detect malicious activity over SSL than IP or domain based IOCs. Since JA3 detects the client application, it doesn’t matter if malware uses DGA (Domain Generation Algorithms), or different IPs for each C2 host, or even if the malware uses Twitter for C2, JA3 can detect the malware itself based on how it communicates rather than what it communicates to.

JA3 is also an excellent detection mechanism in locked-down environments where only a few specific applications are allowed to be installed. In these types of environments one could build a whitelist of expected applications and then alert on any other JA3 hits.

For more details on what you can see and do with JA3 and JA3S, please see this Shmoocon 2018 talk:

Client Hello Audit Record

message TLSClientHello {
string Timestamp = 1;
int32 Type = 2;
int32 Version = 3;
int32 MessageLen = 4;
int32 HandshakeType = 5;
uint32 HandshakeLen = 6;
int32 HandshakeVersion = 7;
bytes Random = 8;
uint32 SessionIDLen = 9;
bytes SessionID = 10;
int32 CipherSuiteLen = 11;
int32 ExtensionLen = 12;
string SNI = 13;
bool OSCP = 14;
repeated int32 CipherSuites = 15;
repeated int32 CompressMethods = 16;
repeated int32 SignatureAlgs = 17;
repeated int32 SupportedGroups = 18;
repeated int32 SupportedPoints = 19;
repeated string ALPNs = 20;
string Ja3 = 21;
string SrcIP = 22;
string DstIP = 23;
string SrcMAC = 24;
string DstMAC = 25;
int32 SrcPort = 26;
int32 DstPort = 27;
repeated int32 Extensions = 28;

JA3S Details

JA3S is JA3 for the Server side of the SSL/TLS communication and fingerprints how servers respond to particular clients.

JA3S uses the following field order:


With JA3S it is possible to fingerprint the entire cryptographic negotiation between client and it's server by combining JA3 + JA3S. That is because servers will respond to different clients differently but will always respond to the same client the same.

For the Trickbot example:

JA3 = 6734f37431670b3ab4292b8f60f29984 ( Fingerprint of Trickbot )
JA3S = 623de93db17d313345d7ea481e7443cf ( Fingerprint of Command and Control Server Response )

For the Emotet example:

JA3 = 4d7a28d6f2263ed61de88ca66eb011e3 ( Fingerprint of Emotet )
JA3S = 80b3a14bccc8598a1f3bbe83e71f735f ( Fingerprint of Command and Control Server Response )

In these malware examples, the command and control server always responds to the malware client in exactly the same way, it does not deviate. So even though the traffic is encrypted and one may not know the command and control server's IPs or domains as they are constantly changing, we can still identify, with reasonable confidence, the malicious communication by fingerprinting the TLS negotiation between client and server. Again, please be aware that these are examples, not indicative of all versions ever, and are intended to illustrate what is possible.

Server Hello Audit Record

message TLSServerHello {
string Timestamp = 1;
int32 Version = 2;
bytes Random = 3;
bytes SessionID = 4;
int32 CipherSuite = 5;
int32 CompressionMethod = 6;
bool NextProtoNeg = 7;
repeated string NextProtos = 8;
bool OCSPStapling = 9;
bool TicketSupported = 10;
bool SecureRenegotiationSupported = 11;
bytes SecureRenegotiation = 12;
string AlpnProtocol = 13;
bool Ems = 14;
repeated bytes Scts = 15;
int32 SupportedVersion = 16;
bool SelectedIdentityPresent = 18;
int32 SelectedIdentity = 19;
bytes Cookie = 20;
int32 SelectedGroup = 21;
repeated int32 Extensions = 22;
string SrcIP = 23;
string DstIP = 24;
string SrcMAC = 25;
string DstMAC = 26;
int32 SrcPort = 27;
int32 DstPort = 28;
string Ja3s = 29;