Documentation
¶
Overview ¶
Package emv decodes EMV BER-TLV structures from contactless and contact payment card APDU responses. Pure offline parser — no hardware, no network — so the same code paths run in unit tests and in any host-side tooling that consumes captured EMV data (saved NFC reads, debugger transcripts, EMV Co specification examples).
Wrap-vs-native judgement: EMV BER-TLV is a well-documented public format (EMV Book 3 §B Annex B). The walker is ~100 lines of bit-twiddling over a byte slice. Wrapping a FAP for this would add an SD-card install step + a firmware-fork dependency for what is, ultimately, a recursive descent parser. We implement natively here so operators can decode an EMV transcript they pasted from a forum post without a Flipper attached.
What this package covers:
- BER-TLV walker with multi-byte tag + length support
- Constructed vs primitive recognition (per the BER class+P/C bit)
- Curated tag-name table for the ~80 most-common EMV tags
What this package does NOT cover (deliberately out of scope):
- Cryptogram verification (Application Cryptogram derivation, CDA, DDA — these need issuer public keys we don't have)
- Online authorisation flow (issuer scripting, ARPC)
- TLV write / re-encode (round-tripping a tree back to bytes — happy to add if a caller materialises)
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Encode ¶ added in v0.382.0
Encode serialises a list of TLVs back into EMV BER-TLV bytes — the inverse of ParseBytes. For each TLV it emits the tag bytes, the definite length (minimal short/long form), and the value. Whether a tag is constructed is taken from its own P/C bit (0x20 of the first tag byte), exactly as Parse reads it: constructed tags are rebuilt from Children, primitive tags from Value. So Encode(Parse(x)) reproduces a minimally-encoded x.
Wrap-vs-native judgement ¶
Native, and the inverse of the existing parser. EMV BER-TLV is a fully public, deterministic structure (ISO/IEC 8825-1 BER + EMV Book 3); encoding is pure tag/length/value byte assembly — no crypto, no hardware. It builds the TLV blobs an operator sends to a card (PDOL/GPO/command data) or stages for a response; generation only, no card I/O. Correctness is verifiable two ways: round-trip against ParseBytes and hand-computed TLV bytes.
Types ¶
type AFL ¶ added in v0.416.0
type AFL struct {
Entries []AFLEntry `json:"entries"`
ReadRecords []ReadRecord `json:"read_records"`
TotalRecords int `json:"total_records"`
}
AFL is a decoded EMV Application File Locator (tag 94), returned by the card in the GET PROCESSING OPTIONS response. It drives which records the terminal reads next.
func DecodeAFL ¶ added in v0.416.0
DecodeAFL decodes the raw bytes of an EMV Application File Locator. The AFL is a sequence of 4-byte entries — [SFI<<3 | 0][first record][last record] [ODA record count] — with no checksum, so correctness is gated structurally: the length must be a non-zero multiple of 4, each SFI must be 1-30, the record range must be ascending, and the ODA count cannot exceed the range. A blob that fails any of these is rejected rather than mis-decoded.
func DecodeAFLHex ¶ added in v0.416.0
DecodeAFLHex is the hex-string convenience wrapper.
type AFLEntry ¶ added in v0.416.0
type AFLEntry struct {
SFI int `json:"sfi"`
FirstRecord int `json:"first_record"`
LastRecord int `json:"last_record"`
ODARecords int `json:"oda_records"`
Records []int `json:"records"`
}
AFLEntry is one 4-byte entry of an EMV Application File Locator: a short file identifier (SFI) and the inclusive record range the terminal must READ RECORD from it, plus how many of those records participate in offline data authentication (ODA).
type AIP ¶ added in v0.717.0
type AIP struct {
Raw string `json:"raw"` // the 2 bytes, hex
Byte1 string `json:"byte1"` // 0xNN
Byte2 string `json:"byte2"` // 0xNN
// Byte-1 capability bits (EMV Book 3 Annex C1).
SDA bool `json:"sda_supported"` // bit 7 (0x40)
DDA bool `json:"dda_supported"` // bit 6 (0x20)
CardholderVerification bool `json:"cardholder_verification_supported"` // bit 5 (0x10)
TerminalRiskManagement bool `json:"terminal_risk_management_to_perform"` // bit 4 (0x08)
IssuerAuthentication bool `json:"issuer_authentication_supported"` // bit 3 (0x04)
OnDeviceCVM bool `json:"on_device_cardholder_verif_supported"` // bit 2 (0x02), EMV 4.3+
CDA bool `json:"cda_supported"` // bit 1 (0x01)
// Capabilities is the human-readable list of the set byte-1 bits, in
// bit order, for a quick read of what the card advertises.
Capabilities []string `json:"capabilities"`
// OfflineDataAuthentication summarises the SDA/DDA/CDA story — the
// single most security-relevant takeaway from an AIP.
OfflineDataAuthentication string `json:"offline_data_authentication"`
Notes []string `json:"notes,omitempty"`
}
AIP is a decoded EMV Application Interchange Profile (tag 82): the 2-byte bitfield in which the card advertises which authentication and verification capabilities it supports. Byte 1's bits are defined unambiguously in EMV 4.3 Book 3, Annex C1 (Table 41); byte 2 is RFU in the contact profile and is repurposed by individual contactless kernels, so it is surfaced raw and not interpreted.
func DecodeAIP ¶ added in v0.717.0
DecodeAIP decodes the raw bytes of EMV tag 82 (Application Interchange Profile). The AIP is a fixed 2-byte bitfield, so it is gated structurally: exactly 2 bytes must be present. Byte 1's seven defined capability bits are decoded per EMV Book 3 Annex C1; byte 1 bit 8 and the whole of byte 2 are RFU in the contact profile and are surfaced raw with a note rather than guessed (no confidently-wrong output).
func DecodeAIPHex ¶ added in v0.717.0
DecodeAIPHex is the hex-string convenience wrapper.
type CVMList ¶ added in v0.426.0
type CVMList struct {
AmountX uint32 `json:"amount_x"`
AmountY uint32 `json:"amount_y"`
Rules []CVMRule `json:"rules"`
Notes []string `json:"notes,omitempty"`
}
CVMList is a decoded EMV Cardholder Verification Method List (tag 8E): two 4-byte amount fields (X and Y, referenced by the per-rule conditions) and a sequence of 2-byte rules.
func DecodeCVMList ¶ added in v0.426.0
DecodeCVMList decodes the raw bytes of EMV tag 8E (CVM List). The layout is fixed — 4-byte Amount X, 4-byte Amount Y, then 2-byte rules — so it is gated structurally: at least the 8-byte amount header must be present and the remaining bytes must be an even number of rule bytes. Each rule's method and condition bytes are always surfaced raw; the EMV-table name is added as a best-effort label, with codes outside the table flagged rather than guessed.
func DecodeCVMListHex ¶ added in v0.426.0
DecodeCVMListHex is the hex-string convenience wrapper.
type CVMResults ¶ added in v0.728.0
type CVMResults struct {
Raw string `json:"raw"` // the 3 bytes, hex
CVMPerformedByte string `json:"cvm_performed_byte"`
CVMCode int `json:"cvm_code"` // low 6 bits
CVMPerformed string `json:"cvm_performed"`
ApplyNextIfUnsuccessful bool `json:"apply_next_if_unsuccessful"` // bit 7 (0x40)
CVMConditionByte string `json:"cvm_condition_byte"`
CVMCondition string `json:"cvm_condition"`
ResultByte string `json:"result_byte"`
Result string `json:"result"` // Unknown / Failed / Successful
Notes []string `json:"notes,omitempty"`
}
CVMResults is a decoded EMV Cardholder Verification Method Results (tag 9F34): the 3-byte field recording which CVM the terminal actually performed and its outcome. It is the companion to the CVM List (tag 8E, nfc_emv_cvm_decode): the List is what the card asks for, the Results are what happened. The CVM-Performed and CVM-Condition bytes use the same encoding as a CVM List rule (EMV Book 3); the 1-byte result is EMV Book 4.
func DecodeCVMResults ¶ added in v0.728.0
func DecodeCVMResults(raw []byte) (*CVMResults, error)
DecodeCVMResults decodes the raw bytes of EMV tag 9F34 (CVM Results). The layout is fixed — CVM Performed, CVM Condition, CVM Result — so it is gated to exactly 3 bytes. The method and condition reuse the same EMV Book 3 tables as the CVM List decoder; a code outside the standard table is flagged RFU/payment-system-specific rather than guessed (no confidently-wrong output).
func DecodeCVMResultsHex ¶ added in v0.728.0
func DecodeCVMResultsHex(s string) (*CVMResults, error)
DecodeCVMResultsHex is the hex-string convenience wrapper.
type CVMRule ¶ added in v0.426.0
type CVMRule struct {
Raw string `json:"raw"` // the 2 bytes, hex
MethodByte string `json:"method_byte"`
MethodCode int `json:"method_code"` // low 6 bits
Method string `json:"method"`
ApplyNextIfUnsuccessful bool `json:"apply_next_if_unsuccessful"` // bit 7 (0x40): else fail CVM
ConditionByte string `json:"condition_byte"`
Condition string `json:"condition"`
}
CVMRule is one Cardholder Verification Method rule from a CVM List (tag 8E): a method byte + a condition byte.
type DOL ¶ added in v0.415.0
type DOL struct {
Entries []DOLEntry `json:"entries"`
Count int `json:"count"`
TotalLength int `json:"total_length"`
}
DOL is a decoded EMV Data Object List — PDOL (tag 9F38), CDOL1/CDOL2 (8C / 8D), DDOL (9F49), or TDOL (97). TotalLength is the size of the concatenated value field the terminal must build and hand back (e.g. the GPO command data assembled from a PDOL).
func DecodeDOL ¶ added in v0.415.0
DecodeDOL decodes the raw bytes of an EMV Data Object List: a concatenation of (BER tag, BER length) pairs with NO value bytes between them. This is why the BER-TLV walker can't parse a DOL — there are no values to walk — and why tag 9F38/8C/8D's value is left raw. Tag names are resolved from the same curated table the TLV walker uses; the parse is purely structural (tag + length header bytes) so there is nothing to mis-decode.
func DecodeDOLHex ¶ added in v0.415.0
DecodeDOLHex is the hex-string convenience wrapper.
type DOLEntry ¶ added in v0.415.0
type DOLEntry struct {
Tag uint32 `json:"tag"`
TagHex string `json:"tag_hex"`
Name string `json:"name,omitempty"`
Length int `json:"length"`
}
DOLEntry is one (tag, length) request inside an EMV Data Object List. A DOL carries no values — only the tags the card asks the terminal to supply and how many bytes each must occupy.
type Magstripe ¶ added in v0.453.0
type Magstripe struct {
Track1 *Track1 `json:"track1,omitempty"`
Track2 *Track2 `json:"track2,omitempty"`
Notes []string `json:"notes,omitempty"`
}
Magstripe is the parsed contents of a raw magnetic-stripe swipe — the ASCII track data a card reader / MSR / skimmer emits, as opposed to the EMV chip's tag-57 BCD Track-2-Equivalent (see DecodeTrack2). It carries Track 1 and/or Track 2 as present in the input.
func DecodeMagstripe ¶ added in v0.453.0
DecodeMagstripe parses a raw swipe string containing Track 1 (starting '%', ending '?') and/or Track 2 (starting ';', ending '?'), in any order. The trailing LRC character (after '?') is surfaced raw but not validated — its check is on the bit-level 5/7-bit encoding, a layer below the ASCII string a reader emits, and a wrong verdict is worse than none.
type ReadRecord ¶ added in v0.416.0
ReadRecord is one implied READ RECORD command (SFI + record number) the terminal issues to walk the AFL.
type TLV ¶
type TLV struct {
// Tag is the BER tag as a uint32 — the encoded big-endian bytes
// of the tag identifier. Single-byte tags use the low byte;
// multi-byte tags pack into successively higher bytes (so 0x9F02
// is the most common Amount Authorised tag, 0x5F2A is the
// Transaction Currency Code, etc.). Stored as uint32 so the
// tag-name lookup map can key on it without a string conversion.
Tag uint32 `json:"tag"`
// TagHex is the operator-facing rendering of Tag — always
// uppercase, no 0x prefix, no leading zeros. Matches the format
// every EMV book / forum post uses ("9F02", "5F2A").
TagHex string `json:"tag_hex"`
// Name is the canonical EMV name for the tag, or "" when the
// tag isn't in the curated lookup table.
Name string `json:"name,omitempty"`
// Constructed reports whether the value bytes contain nested
// TLVs (per BER class+P/C bit). When true, Children holds the
// parsed sub-tree and Value is the raw bytes (kept for callers
// that want to re-emit the original structure).
Constructed bool `json:"constructed"`
Value []byte `json:"value,omitempty"`
// ValueHex is the operator-facing hex rendering of Value.
// Convenient for JSON output without forcing every caller to
// re-encode.
ValueHex string `json:"value_hex,omitempty"`
// Children is non-nil iff Constructed is true. May be empty
// when a constructed tag's body is zero-length (legal per the
// spec, occasionally seen in templated responses).
Children []TLV `json:"children,omitempty"`
}
TLV is one decoded BER-TLV entry. Constructed entries carry their child TLVs in Children; primitive entries carry the raw value bytes in Value (Children is nil).
func Parse ¶
Parse decodes a hex-encoded EMV BER-TLV blob (the common form operator-supplied EMV captures take) into a flat list of top-level TLVs. Constructed tags are walked recursively into each TLV's Children. Returns an error on malformed input (truncated tag/length, length-exceeds-buffer, length-encoding reserved value).
func ParseBytes ¶
ParseBytes is the byte-slice variant of Parse for callers that already have raw EMV bytes (e.g. from a PC/SC reader). Same recursive walker; same error contract.
type TSI ¶ added in v0.720.0
type TSI struct {
Raw string `json:"raw"` // the 2 bytes, hex
Bytes []string `json:"bytes"` // each byte, 0xNN
// FunctionsPerformed lists the set bits — each a function the terminal
// performed — in bit order (byte 1, high to low).
FunctionsPerformed []string `json:"functions_performed"`
// NonePerformed reports an all-zero TSI: the terminal recorded no
// completed function (e.g. a transaction aborted very early).
NonePerformed bool `json:"none_performed"`
Notes []string `json:"notes,omitempty"`
}
TSI is a decoded EMV Transaction Status Information (tag 9B): the 2-byte bitfield in which the terminal records which functions it actually performed during a transaction. It is the third member of the EMV transaction-outcome trio — the AIP (tag 82) says what the card can do, the TVR (tag 95) records what the terminal flagged, and the TSI records what the terminal carried out. The bit meanings are defined in EMV 4.3 Book 3, Annex C6; both bytes are terminal-defined and stable across payment systems.
func DecodeTSI ¶ added in v0.720.0
DecodeTSI decodes the raw bytes of EMV tag 9B (Transaction Status Information). The TSI is a fixed 2-byte bitfield, so it is gated structurally: exactly 2 bytes must be present. Byte 1's six defined bits are decoded per EMV Book 3 Annex C6; byte 1 bits 2/1 and the whole of byte 2 are RFU and are surfaced via a note rather than named (no confidently-wrong output).
func DecodeTSIHex ¶ added in v0.720.0
DecodeTSIHex is the hex-string convenience wrapper.
type TVR ¶ added in v0.718.0
type TVR struct {
Raw string `json:"raw"` // the 5 bytes, hex
Bytes []string `json:"bytes"` // each byte, 0xNN
// Clean reports that no exception bit is set across all 5 bytes — the
// terminal flagged nothing.
Clean bool `json:"clean"`
// Each group lists the set bits for one functional byte (omitted when
// that byte is zero). Grouped per EMV Book 3 Annex C5's own byte layout.
OfflineDataAuthentication []string `json:"offline_data_authentication,omitempty"` // byte 1
ApplicationUsage []string `json:"application_usage,omitempty"` // byte 2
CardholderVerification []string `json:"cardholder_verification,omitempty"` // byte 3
TerminalRiskManagement []string `json:"terminal_risk_management,omitempty"` // byte 4
IssuerScriptProcessing []string `json:"issuer_script_processing,omitempty"` // byte 5
// Indications is the flat list of every set defined bit, in byte/bit
// order — a one-glance read of everything the terminal flagged.
Indications []string `json:"indications"`
Notes []string `json:"notes,omitempty"`
}
TVR is a decoded EMV Terminal Verification Results (tag 95): the 5-byte bitfield in which the terminal records the outcome of every check it ran during a transaction — which offline-authentication, application-usage, cardholder-verification, risk-management and issuer-script steps passed, failed or were skipped. Every set bit is an exception the terminal flagged, so a TVR of all zeroes means a clean transaction. The bit meanings are defined in EMV 4.3 Book 3, Annex C5; all five bytes are terminal-defined and stable across payment systems (no contactless-kernel reinterpretation).
func DecodeTVR ¶ added in v0.718.0
DecodeTVR decodes the raw bytes of EMV tag 95 (Terminal Verification Results). The TVR is a fixed 5-byte bitfield, so it is gated structurally: exactly 5 bytes must be present. Each byte's defined bits are decoded per EMV Book 3 Annex C5; any RFU bit that is set is surfaced via a note rather than named (no confidently-wrong output).
func DecodeTVRHex ¶ added in v0.718.0
DecodeTVRHex is the hex-string convenience wrapper.
type Track1 ¶ added in v0.453.0
type Track1 struct {
FormatCode string `json:"format_code"` // 'B' = financial/bank
PAN string `json:"pan"`
PANMasked string `json:"pan_masked"`
Name string `json:"name,omitempty"`
Surname string `json:"surname,omitempty"`
GivenName string `json:"given_name,omitempty"`
Expiry string `json:"expiry,omitempty"` // raw YYMM
ExpiryFormatted string `json:"expiry_mm_yy,omitempty"` // MM/YY
ServiceCode string `json:"service_code,omitempty"`
ServiceCodeMeaning string `json:"service_code_meaning,omitempty"`
Discretionary string `json:"discretionary_data,omitempty"`
LuhnValid bool `json:"luhn_valid"`
LRC string `json:"lrc,omitempty"` // trailing redundancy char, surfaced raw (not validated)
Notes []string `json:"notes,omitempty"`
}
Track1 is the decoded ISO 7813 Track 1 (IATA) format — the only track that carries the cardholder name and a format code.
type Track2 ¶ added in v0.414.0
type Track2 struct {
PAN string `json:"pan"`
PANMasked string `json:"pan_masked"`
Expiry string `json:"expiry"` // raw YYMM as encoded
ExpiryFormatted string `json:"expiry_mm_yy"` // MM/YY
ServiceCode string `json:"service_code"` // 3 digits
ServiceCodeMeaning string `json:"service_code_meaning,omitempty"`
Discretionary string `json:"discretionary_data,omitempty"`
LuhnValid bool `json:"luhn_valid"`
Notes []string `json:"notes,omitempty"`
}
Track2 is the decoded contents of EMV tag 57 (Track 2 Equivalent Data) / ISO 7813 track 2. The BER-TLV walker in this package surfaces tag 57's raw value bytes but leaves the nibble-packed track structure untouched; DecodeTrack2 cracks it into the security-relevant fields.
func DecodeTrack2 ¶ added in v0.414.0
DecodeTrack2 decodes the raw bytes of EMV tag 57 (Track 2 Equivalent Data). The format is nibble-packed BCD: <PAN> 'D' <YYMM expiry> <3-digit service code> <discretionary data> with an optional trailing 'F' pad nibble. The PAN's trailing Luhn check digit is the verification anchor — the decode is reported with luhn_valid so a misframed blob is surfaced, never asserted as a valid card number.
func DecodeTrack2Hex ¶ added in v0.414.0
DecodeTrack2Hex is the hex-string convenience wrapper.