emv

package
v0.783.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2026 License: AGPL-3.0 Imports: 4 Imported by: 0

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

func Encode(tlvs []TLV) ([]byte, error)

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.

func IsDOLTag added in v0.415.0

func IsDOLTag(tag uint32) bool

IsDOLTag reports whether tag is one of the EMV Data Object List tags (PDOL / CDOL1 / CDOL2 / DDOL / TDOL).

func TagName

func TagName(tag uint32) string

TagName returns the canonical EMV name for a tag, or the empty string when the tag isn't in the curated table. Lookups are case-insensitive on the encoded form because the input is a uint32 (no character-case ambiguity).

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

func DecodeAFL(raw []byte) (*AFL, error)

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

func DecodeAFLHex(s string) (*AFL, error)

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

func DecodeAIP(raw []byte) (*AIP, error)

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

func DecodeAIPHex(s string) (*AIP, error)

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

func DecodeCVMList(raw []byte) (*CVMList, error)

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

func DecodeCVMListHex(s string) (*CVMList, error)

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

func DecodeDOL(raw []byte) (*DOL, error)

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

func DecodeDOLHex(s string) (*DOL, error)

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

func DecodeMagstripe(s string) (*Magstripe, error)

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

type ReadRecord struct {
	SFI    int `json:"sfi"`
	Record int `json:"record"`
}

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

func Parse(hexBlob string) ([]TLV, error)

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

func ParseBytes(b []byte) ([]TLV, error)

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

func DecodeTSI(raw []byte) (*TSI, error)

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

func DecodeTSIHex(s string) (*TSI, error)

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

func DecodeTVR(raw []byte) (*TVR, error)

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

func DecodeTVRHex(s string) (*TVR, error)

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

func DecodeTrack2(raw []byte) (*Track2, error)

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

func DecodeTrack2Hex(s string) (*Track2, error)

DecodeTrack2Hex is the hex-string convenience wrapper.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL