Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Assembly (x86-64, AArch64, RISC-V)  Module 15 – Security at the machine level (defensive)

Constant-time code and data-independent timing

Find where a secret steers a branch or an address in compiler output, write a comparison that runs the same instructions for every input, and check it.

  • Advanced
  • 15 minutes
  • Examples run with Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 6 of 15 minutes free
Free: A secret leaks through what the code does with it · An early exit tells the timing how many bytes matched · The fixed compare reads every byte and decides once · Practise. With a pass: 2 in-depth parts and 3 interview answers, about 9 minutes.

What you will learn

  • Explain how a branch or a memory address chosen by a secret shows up in the timing
  • Write a comparison whose instruction count is the same for every input of one length
  • Check a compiled comparison with an instruction count that fails on the weak version
  • Use Apple's DIT setting and describe the leaks it leaves to your code

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain how a branch or a memory address chosen by a secret shows up in the timing
  • Write a comparison whose instruction count is the same for every input of one length
  • Check a compiled comparison with an instruction count that fails on the weak version
  • Use Apple's DIT setting and describe the leaks it leaves to your code

Before you start

On this page

A tag check that returns at the first wrong byte ran 7 instructions when byte 0 was wrong and 97 when only byte 15 was, in the run recorded below. Anyone who can time many such checks learns how many leading bytes of a guess were right, which turns one hopeless guess at 16 bytes into sixteen small ones.

Constant-time code closes that gap for keys, password hashes and authentication tags: the instructions it runs and the addresses it reads depend only on public values such as a length. This page finds the leak, writes the fix and checks it with a count that fails on the weak version.

A secret leaks through what the code does with it

A side channel: A way to learn a secret not from the data itself but from a measurable effect of handling it, such as how long an operation took or whether a cache line was loaded. More in “Branch prediction and speculative execution” is anything measurable about how a computation ran, apart from its result. For code that holds a secret value, the one that matters most is time, and a secret reaches the time by two routes.

Interactive · step by step Two ways a secret reaches the timing Step through one secret value. The left column is a branch on it, the right column an address made from it.
Two ways a secret reaches the timing, step 4 of 4. Showing a secret value, a branch on it, an address from it, which code runs, memory it reads, data ops only. In focus: data ops only.a secret valuea key byte or a taga branch on itif, early return, &&an address from ittable[secret]which code runsstep count changesmemory it readscache use changesdata ops onlyXOR, AND, OR, masks

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. One value that must not leak. A key byte, a received tag or a password is a secret: nothing outside the function should learn anything about it beyond what the result already says.
  2. A branch on it. When a condition depends on a secret, the processor runs different instructions for different values. The step count, and with it the time, changes with that value.
  3. An address made from it. When a secret picks an address, the same instructions run every time, but a different memory location is read. What the cache then holds, and how fast a later read is, depends on that value.
  4. Keep it in data operations. Constant-time code lets secrets flow only through operations such as XOR, AND, OR and masks, never into a branch condition or an address, so the path and the memory read are the same for every value.

A branch whose condition depends on a secret runs different instructions for different values, so the step count moves with it. An address computed from a secret, such as table[key], runs the same instructions but reads a different place in memory, and what the cache holds afterwards depends on that value: a cache-timing leak: A side channel in which a secret picks which memory a program reads, so what the cache holds afterwards, and how fast later reads are, depends on that secret even though the same instructions ran. leak. constant-time code: Code whose instructions and memory addresses depend only on public values, such as a length, so its running time reveals nothing about the secrets it handles beyond what its result already says. keeps secrets out of both, letting them flow only through data operations such as XOR, AND, OR and masks.

Checkpoint

Check: How a secret reaches the timing

  1. Question 1 of 2 Which of these lets a secret change how many instructions a function runs?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A conditional branch whose condition depends on a secret

    A branch on a secret is the first of the two routes: the path, and so the count of instructions, depends on the value. The second route is an address computed from a secret.

  2. Question 2 of 2 A function returns table[key], where key is secret, and runs the same instructions for every key. Is it constant time?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: No: the address it reads depends on key, so what the cache holds afterwards depends on key

    Constant-time code keeps secrets out of branch conditions and out of addresses. A lookup indexed by a secret fails the second rule even though its instruction count never moves.

An early exit tells the timing how many bytes matched

compare.c checks one 16-byte tag three ways; count_steps.py adds an LLDB command that counts the instructions of one call.

Three comparisons with the same answers, and the step counter

compare.c

/* Three ways to compare a received 16-byte tag with the expected one. All three return the same answers; they differ
   in how much work they do, and so in how long they take, depending on where the first difference is. */
#include <stdio.h>
#include <string.h>

#define TAG 16

/* Weak: returns at the first difference, so its running time says how many leading bytes matched. */
__attribute__((noinline)) int tag_eq_early(const unsigned char *a, const unsigned char *b, size_t n) {
    for (size_t i = 0; i < n; i++) {
        if (a[i] != b[i]) {
            return 0;
        }
    }
    return 1;
}

/* Looks branch-free, but && stops evaluating once eq is 0, and the compiler may branch on that. */
__attribute__((noinline)) int tag_eq_andand(const unsigned char *a, const unsigned char *b, size_t n) {
    int eq = 1;
    for (size_t i = 0; i < n; i++) {
        eq = eq && (a[i] == b[i]);
    }
    return eq;
}

/* Fixed: every byte is read and combined with OR, and the only branch is the loop's own test on n. */
__attribute__((noinline)) int tag_eq_ct(const unsigned char *a, const unsigned char *b, size_t n) {
    unsigned char diff = 0;
    for (size_t i = 0; i < n; i++) {
        diff |= (unsigned char)(a[i] ^ b[i]);
    }
    return diff == 0;
}

typedef int (*compare_fn)(const unsigned char *, const unsigned char *, size_t);

/* Runs one comparison on the three inputs in a fixed order: equal tags, a difference at byte 0, one at byte 15. */
static void show(const char *name, compare_fn eq, const unsigned char *expected, const unsigned char *first,
                 const unsigned char *last) {
    int same = eq(expected, expected, TAG);
    int at0 = eq(expected, first, TAG);
    int at15 = eq(expected, last, TAG);
    printf("%s: %d %d %d\n", name, same, at0, at15);
}

int main(void) {
    unsigned char expected[TAG], first[TAG], last[TAG];
    memset(expected, 0x5a, TAG);
    memcpy(first, expected, TAG);
    memcpy(last, expected, TAG);
    first[0] ^= 1;          /* differs at byte 0 */
    last[TAG - 1] ^= 1;     /* differs at byte 15 */
    show("early exit", tag_eq_early, expected, first, last);
    show("&& form   ", tag_eq_andand, expected, first, last);
    show("OR form   ", tag_eq_ct, expected, first, last);
    return 0;
}

count_steps.py

# An LLDB command, `count`, that steps the stopped function one instruction at a time until it returns to its caller,
# and prints how many instructions ran. A constant-time function runs the same number of instructions for every input
# of the same length; a function with a branch on the data does not.
#
# Use: lldb -b -o 'command script import count_steps.py' -o 'breakpoint set -n f' -o run -o count -o continue ...
import lldb


def count(debugger, command, result, internal_dict):
    thread = debugger.GetSelectedTarget().GetProcess().GetSelectedThread()
    name = thread.GetFrameAtIndex(0).GetFunctionName()
    depth = thread.GetNumFrames()
    steps = 0
    while thread.GetNumFrames() >= depth and steps < 100000:
        thread.StepInstruction(True)
        steps += 1
    result.AppendMessage(f"{name}: {steps} instructions")


def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand("command script add -f count_steps.count count")
Interactive · step by step Where each comparison stops Step through the three recorded inputs. The left column is the early-exit compare, the right the OR form.
Where each comparison stops, step 4 of 4. Showing two 16-byte tags, early exit, OR form, 7, 97 or 99 steps, 37 steps, always. In focus: 7, 97 or 99 steps, 37 steps, always.two 16-byte tagsexpected, receivedearly exitreturns earlyOR formreads all 16 pairs7, 97 or 99 stepsdepends on input37 steps, alwaysset by the length

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. The same question three times. compare.c checks one expected tag against three received ones: an equal copy, one that differs at byte 0 and one that differs at byte 15. All three functions give the same answers, 1, 0 and 0.
  2. The early exit stops early. tag_eq_early returns at the first byte that differs. The recorded count was 7 instructions when byte 0 differed, 97 when only byte 15 did, and 99 for equal tags.
  3. The OR form reads every pair. tag_eq_ct combines every pair of bytes with XOR and OR, and tests the total once at the end. It ran 37 instructions for each of the three inputs.
  4. What the count gives away. The early exit count says how many leading bytes matched, so the timing of many guesses can confirm a tag one byte at a time. The OR form count says only the length, which is public.

Three comparisons, the early exit's listing and the instruction count Python · ct_session.py
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session

session.run("""
# Build the comparisons and check that they agree: equal tags, a difference at byte 0, a difference at byte 15
clang -O2 -o compare compare.c
./compare
""")

session.run("""
# The early exit's listing: b.ne straight after cmp w8, w9 is a branch on the bytes themselves
otool -tv compare | awk '/^_tag_eq_early:/,/^_tag_eq_andand:/' | grep -v '^_tag_eq_andand:'
""")

session.run("""
# The check: count the instructions each call runs. Same tag length, three inputs, so the counts should not differ
lldb -b -o 'command script import count_steps.py' -o 'breakpoint set -n tag_eq_early' -o run -o count -o continue -o count -o continue -o count -o continue ./compare 2>&1 | grep instructions
lldb -b -o 'command script import count_steps.py' -o 'breakpoint set -n tag_eq_ct' -o run -o count -o continue -o count -o continue -o count -o continue ./compare 2>&1 | grep instructions
""")

Output

# Build the comparisons and check that they agree: equal tags, a difference at byte 0, a difference at byte 15
$ clang -O2 -o compare compare.c
$ ./compare
early exit: 1 0 0
&& form   : 1 0 0
OR form   : 1 0 0
# The early exit's listing: b.ne straight after cmp w8, w9 is a branch on the bytes themselves
$ otool -tv compare | awk '/^_tag_eq_early:/,/^_tag_eq_andand:/' | grep -v '^_tag_eq_andand:'
_tag_eq_early:
0000000100000460	cbz	x2, 0x10000047c
0000000100000464	ldrb	w8, [x0], #0x1
0000000100000468	ldrb	w9, [x1], #0x1
000000010000046c	cmp	w8, w9
0000000100000470	b.ne	0x100000484
0000000100000474	subs	x2, x2, #0x1
0000000100000478	b.ne	0x100000464
000000010000047c	mov	w0, #0x1
0000000100000480	ret
0000000100000484	mov	w0, #0x0
0000000100000488	ret
# The check: count the instructions each call runs. Same tag length, three inputs, so the counts should not differ
$ lldb -b -o 'command script import count_steps.py' -o 'breakpoint set -n tag_eq_early' -o run -o count -o continue -o count -o continue -o count -o continue ./compare 2>&1 | grep instructions
tag_eq_early: 99 instructions
tag_eq_early: 7 instructions
tag_eq_early: 97 instructions
$ lldb -b -o 'command script import count_steps.py' -o 'breakpoint set -n tag_eq_ct' -o run -o count -o continue -o count -o continue -o count -o continue ./compare 2>&1 | grep instructions
tag_eq_ct: 37 instructions
tag_eq_ct: 37 instructions
tag_eq_ct: 37 instructions

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 ct_session.py

In the listing, cmp w8, w9 compares one pair of bytes and the next instruction, b.ne, leaves the loop on a difference, so the bytes decide how long the loop runs. The count shows it: 7 instructions when byte 0 differed, 97 when only byte 15 did, 99 for equal tags. Two of those calls return the same 0, yet their times differ, and the time is what leaks. The shape to look for in any listing is the same: a conditional branch inside the loop whose flags come from the loaded bytes, not from the counter.

The fixed compare reads every byte and decides once

tag_eq_ct reads every pair, folds each difference into one byte with XOR and OR, and tests that byte once, after the loop. Its branches test only the length, which is public, and it ran 37 instructions for all three inputs. That is the check: one function, one length, several inputs and a count that must not move; it fails on the early exit and passes on the fix.

The two comparisons in x86-64 and RISC-V compiler output Python · isa_session.py
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session

session.run(r"""
# The two comparisons on x86-64 and RISC-V. Vectorising and unrolling are off so that each loop is one pass per byte
clang-23 --target=x86_64-linux-gnu -O2 -fno-vectorize -fno-unroll-loops -S -o - compare_portable.c | sed 's/[[:space:]]*#.*//' | grep -E '^[.A-Za-z_][.A-Za-z0-9_]*:|^[[:space:]]+[a-z]'
clang-23 --target=riscv64-linux-gnu -O2 -fno-vectorize -fno-unroll-loops -S -o - compare_portable.c | sed 's/[[:space:]]*#.*//' | grep -E '^[.A-Za-z_][.A-Za-z0-9_]*:|^[[:space:]]+[a-z]'
""", max_lines=80)

Output

# The two comparisons on x86-64 and RISC-V. Vectorising and unrolling are off so that each loop is one pass per byte
$ clang-23 --target=x86_64-linux-gnu -O2 -fno-vectorize -fno-unroll-loops -S -o - compare_portable.c | sed 's/[[:space:]]*#.*//' | grep -E '^[.A-Za-z_][.A-Za-z0-9_]*:|^[[:space:]]+[a-z]'
tag_eq_early:
	movl	$1, %eax
	testq	%rdx, %rdx
	je	.LBB0_5
	xorl	%ecx, %ecx
.LBB0_3:
	movzbl	(%rdi,%rcx), %r8d
	cmpb	(%rsi,%rcx), %r8b
	jne	.LBB0_4
	incq	%rcx
	cmpq	%rcx, %rdx
	jne	.LBB0_3
.LBB0_5:
	retq
.LBB0_4:
	xorl	%eax, %eax
	retq
.Lfunc_end0:
tag_eq_ct:
	testq	%rdx, %rdx
	je	.LBB1_1
	xorl	%ecx, %ecx
	xorl	%eax, %eax
.LBB1_5:
	movzbl	(%rsi,%rax), %r8d
	xorb	(%rdi,%rax), %r8b
	movzbl	%r8b, %r8d
	orl	%r8d, %ecx
	incq	%rax
	cmpq	%rax, %rdx
	jne	.LBB1_5
	xorl	%eax, %eax
	testl	%ecx, %ecx
	sete	%al
	retq
.LBB1_1:
	movl	$1, %eax
	retq
.Lfunc_end1:
$ clang-23 --target=riscv64-linux-gnu -O2 -fno-vectorize -fno-unroll-loops -S -o - compare_portable.c | sed 's/[[:space:]]*#.*//' | grep -E '^[.A-Za-z_][.A-Za-z0-9_]*:|^[[:space:]]+[a-z]'
tag_eq_early:
	mv	a3, a0
	li	a0, 1
	beqz	a2, .LBB0_3
.LBB0_1:
	lbu	a4, 0(a3)
	lbu	a5, 0(a1)
	bne	a4, a5, .LBB0_4
	addi	a2, a2, -1
	addi	a1, a1, 1
	addi	a3, a3, 1
	bnez	a2, .LBB0_1
.LBB0_3:
	ret
.LBB0_4:
	li	a0, 0
	ret
.Lfunc_end0:
tag_eq_ct:
	beqz	a2, .LBB1_4
	li	a3, 0
	add	a2, a2, a1
.LBB1_2:
	lbu	a4, 0(a0)
	lbu	a5, 0(a1)
	xor	a4, a4, a5
	addi	a1, a1, 1
	or	a3, a3, a4
	addi	a0, a0, 1
	bne	a1, a2, .LBB1_2
	seqz	a0, a3
	ret
.LBB1_4:
	li	a0, 1
	ret
.Lfunc_end1:

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 isa_session.py

On x86-64 and RISC-V the early exit also branches straight after the byte comparison, jne after cmpb and bne a4, a5, while the fixed loop branches only on its counter. Where a library offers a routine, prefer it: OpenSSL’s CRYPTO_memcmp, OpenBSD’s timingsafe_bcmp and libsodium’s sodium_memcmp are each documented to take a time set by the length alone.

Two cautions remain. The compiler chooses the instructions, so it can put a branch back into C that has none. And Apple silicon offers data-independent timing: A processor setting, called DIT on Arm, under which a listed set of instructions takes the same time whatever values it processes. It does not change which branches a program takes or which addresses it reads., DIT, a setting under which a listed set of instructions takes the same time whatever values it processes. Data-independent timing leaves the branches taken and the addresses read unchanged, so it adds to constant-time code rather than replacing it.

Checkpoint

Check: What an early exit gives away · The fixed comparison and its count check

  1. Question 1 of 2 In the recorded count, tag_eq_early ran 7 instructions when byte 0 differed and 97 when only byte 15 did. Both calls returned 0. What can someone timing many calls learn?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: How many leading bytes of a guessed tag were right

    The early exit's count depends on where the first difference is. Repeated timed guesses can therefore confirm a tag one byte at a time instead of guessing all 16 bytes at once.

  2. Question 2 of 2 You count a new comparison on three 16-byte inputs: equal, differing at byte 0, differing at byte 15. Which result passes the check?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The same count for all three, as tag_eq_ct gave with 37, 37 and 37

    The check fails on tag_eq_early (99, 7 and 97) and passes on tag_eq_ct (37 each time). Any count that moves with the inputs of one length points to a branch the data decides.

Practise

Copy the page’s files first; setting up the toolchain covers other machines.

  1. Move the difference in last from byte 15 to byte 8, predict the early exit’s count for that call, then run the counter.
  2. Set TAG to 32 and count again: the fixed compare’s count changes, by the same amount for all three inputs.
  3. Fold with subtraction instead, diff |= (unsigned char)(a[i] - b[i]), and confirm that the answers and the equal counts stay.
  4. Build compare_portable.c for RISC-V with the listing command and name the instruction of tag_eq_early whose outcome the bytes decide.

Practice that adapts to you

Practise this lesson

7 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 7 Constant-time code keeps a secret out of which two places?

    Choose one answer.

    Show the answer: Question 1 of 7

    Answer: Branch conditions and memory addresses

    Secrets may flow through data operations such as XOR, AND, OR and masks. They must not decide a branch or pick an address, the two routes by which a secret reaches the timing.

  2. Question 2 of 7 Which line keeps the path and the addresses the same for every value of a secret byte s?

    Choose one answer.

    Show the answer: Question 2 of 7

    Answer: diff |= (unsigned char)(s ^ t);

    Only the XOR-and-OR line keeps s in data operations. The others let it decide a branch, an address or a loop's trip count.

  3. Question 3 of 7 Why did tag_eq_early run 7 instructions for one wrong tag and 97 for another?

    Choose one answer.

    Show the answer: Question 3 of 7

    Answer: It returns at the first differing byte, which was byte 0 in one case and byte 15 in the other

    The early exit's own branch, b.ne straight after the byte comparison, ends the loop at the first difference. The count, and the time, say where that difference was.

  4. Question 4 of 7 The early exit ran 7 instructions when byte 0 differed and 97 when byte 15 did. How many instructions does each matching byte before the difference add?

    Type a number.

    A hint

    Fifteen more bytes matched in the second call than in the first.

    Show the answer: Question 4 of 7

    Answer: 6 instructions

    97 − 7 = 90 more instructions for 15 more matching bytes, so 6 per byte: two loads, cmp, b.ne, subs and the loop's own b.ne. A difference at byte 8 would then take 7 + 8 × 6 = 55.

  5. Question 5 of 7 What keeps tag_eq_ct at 37 instructions for all three inputs?

    Choose one answer.

    Show the answer: Question 5 of 7

    Answer: It reads every pair, folds each difference with XOR and OR, and tests the total once after the loop

    Reading everything and deciding once is the fix: the work is set by the length, which is public, so the count does not move with the contents.

  6. Question 6 of 7 A new comparison counts 37, 37 and 41 instructions on three 16-byte inputs. What do you conclude?

    Choose one answer.

    Show the answer: Question 6 of 7

    Answer: It fails: something the data decides changes the path, so find that branch in the listing

    The check is strict by design: any input of the same length that changes the count shows a branch the data decides. The listing then shows which one.

  7. Question 7 of 7 A colleague says turning on DIT makes the early-exit comparison safe. What is missing from that claim?

    Choose one answer.

    Show the answer: Question 7 of 7

    Answer: DIT fixes the timing of listed instructions, but the early exit still takes a branch the bytes decide

    DIT adds to constant-time code; it does not replace it. A branch on the data still changes how many instructions run, with DIT on or off.

A measured timing gap, and a branch the compiler adds to branch-free C Pro: opens with a Pro, Premium or Ultimate pass

Instruction counts stand in for time, so this part first measures the real gap on longer tags. It then finds a branch the compiler wrote into C that has no if, catches it with the count, and audits every branch the fixed version keeps.

In this part:

  • Measuring the gap the early exit leaves, with numbers
  • Finding the branch a logical AND adds, and the check that catches it
  • Auditing every branch the fixed version keeps

3 runnable examples (C, Python) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

DIT on Apple silicon, and a table lookup it does not fix Pro: opens with a Pro, Premium or Ultimate pass

DIT is the hardware’s half of constant time. This part sets and clears it from user code, uses the system’s enable and restore pair correctly in a nested call, and then shows a leak that both DIT and the instruction count miss, with the check that finds it.

In this part:

  • Turning DIT on, and restoring it in a nested call
  • A table lookup DIT does not fix, and the scan that does

4 runnable examples (Python, S, addr_check.awk, leaks.c) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

A warm-up for freshers: why is memcmp the wrong way to check a received authentication tag?

Because it may stop at the first byte that differs, so its time says how many leading bytes of the received tag were right. Use a comparison whose time depends only on the length: a library routine documented as constant time, such as CRYPTO_memcmp or sodium_memcmp, or a loop that reads every byte, folds the differences with XOR and OR and tests the total once. Either way, check the compiled code, because the compiler decides the branches.

Three harder questions follow: fixing a teammate’s comparison, keeping it constant-time across compilers, and placing DIT.

  1. Mid to senior

    A teammate's change checks the signature on incoming webhooks with a loop that returns false at the first mismatched byte. How do you explain the problem to them, what fix do you ask for, and how do you show that the fix works?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Senior to staff

    Your constant-time tag comparison is correct in C today. How would you keep it constant time across compiler upgrades, new optimisation settings and new build targets, without trusting the source alone?

    Model answer, Pro. First step and follow-ups

  3. Senior to staff

    You maintain a signing routine that runs on Apple silicon. Where would you turn DIT on and off, how do you keep callers that already turned it on working correctly, and what does your own code still have to handle?

    Model answer, Pro. First step and follow-ups

Flashcards · 5 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. How can a secret leak through timing even when every result is correct?

    When a secret value decides a branch or a memory address, the time the code takes, or what it leaves in the cache, depends on that value.

  2. Why is memcmp the wrong way to check a received authentication tag?

    It may stop at the first byte that differs, so its time tells how many leading bytes were right. Use a routine documented as constant time, such as CRYPTO_memcmp or sodium_memcmp, or a loop that reads every byte.

  3. What does a constant-time comparison of two buffers do?

    It reads every byte, folds the differences together with XOR and OR, and tests the total once at the end, so it runs the same instructions for any contents of the same length.

  4. How do you check that a comparison really is constant time?

    From the compiled code: count the instructions it runs on several inputs of the same length and treat any difference as a failure, because the compiler decides which branches exist.

  5. What does DIT on Apple silicon fix, and what does it leave alone?

    It makes a listed set of instructions take the same time whatever values they process. It does not change which branches a program takes or which addresses it reads.

Key takeaways

  • A secret leaks through timing when it decides a branch or an address, even when every result is correct.
  • A constant-time comparison reads every byte, folds the differences and tests the total once, so its count is the same for every input of one length.
  • The evidence is the compiled code: count instructions on several inputs of one length, and treat any difference as a failure.
  • DIT fixes the timing of listed instructions on Apple silicon, not the branches a program takes or the addresses it reads.

Terms in this lesson

cache-timing leak
A side channel in which a secret picks which memory a program reads, so what the cache holds afterwards, and how fast later reads are, depends on that secret even though the same instructions ran.
constant-time code
Code whose instructions and memory addresses depend only on public values, such as a length, so its running time reveals nothing about the secrets it handles beyond what its result already says.
data-independent timing
A processor setting, called DIT on Arm, under which a listed set of instructions takes the same time whatever values it processes. It does not change which branches a program takes or which addresses it reads.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.