munotes®

Cyber and Information Security Notes | B.Sc. (Computer Science) Semester 5 | Mumbai University | munotes

Get access to whole semester resourcesSemester Pass

Official Notes munotes.in

Cyber and Information Security

B.SC. (COMPUTER SCIENCE) · SEMESTER 5

Strictly as per the University of Mumbai NEP syllabus in force for B.Sc. (Computer Science)

For B.Sc. (Computer Science) students of the University of Mumbai and all its affiliated colleges

Open the book ↓

munotes.in Third Year

Cyber and Information Security

Copyright © 2026 munotes.in. All rights reserved.

Written and first published by munotes.in, 2026.

This book is free for individual students to read at munotes.in. No part of it may be reproduced, distributed, stored, translated or used for institutional or classroom purposes in any form without a prior written licence from munotes.in.

Licensing and permissions: contact@munotes.in

The text of statutes and of judgments reproduced in this book is in the public domain under section 52(1)(q) of the Copyright Act 1957. The commentary, arrangement, examples and questions are the original work of munotes.in.

munotes.in is an independent study resource for MU students. It is not affiliated with, endorsed by, or officially connected to the University of Mumbai. Course names and university references describe the students and syllabus the material relates to.

munotes.in

Contents

Module I Introduction, classical encryption, public-key cryptography and RSA, key management, message authentication and hash functions

  1. What Security Means: Confidentiality, Integrity and Availability 1
  2. The Security Trend: Why Attacks Got Easier as Attackers Got Less Skilled 5
  3. The OSI Security Architecture: The Words This Subject Is Spoken In 10
  4. Security Attacks: Passive and Active 15
  5. Security Services: The Five Things Security Promises 20
  6. Security Mechanisms, and Which Service Each One Serves 25
  7. The Symmetric Cipher Model 30
  8. Cryptanalysis and the Attack Models 35
  9. The Caesar Cipher, and Breaking It in Twenty-five Tries 41
  10. Monoalphabetic Substitution, and Breaking It by Frequency 46
  11. The Playfair Cipher 51
  12. The Hill Cipher 57
  13. Polyalphabetic Ciphers: Vigenere and the Autokey 64
  14. The One-Time Pad, and What Perfect Secrecy Means 70
  15. Transposition Techniques: Rail Fence and Row Transposition 75
  16. Steganography: Hiding That There Is a Message at All 81
  17. Block Ciphers and Stream Ciphers: The Difference That Decides Everything 86
  18. The Feistel Cipher Structure, Confusion and Diffusion 91
  19. The Data Encryption Standard: The Shape of It 97
  20. Inside a DES Round: Expansion, the S-boxes and the Permutation 102
  21. The DES Key Schedule 110
  22. DES Run End to End: Decryption, and the Avalanche Effect 116
  23. The Strength of DES: Fifty-six Bits, and the Machines That Broke It 125
  24. The Advanced Encryption Standard 131
  25. Multiple Encryption, Double DES and the Meet-in-the-Middle Attack 138
  26. Triple DES, and Where It Stands Now 145
  27. Electronic Codebook Mode, and Why It Leaks 154
  28. Cipher Block Chaining 160
  29. Cipher Feedback, Output Feedback and Counter Mode 166
  30. Stream Ciphers and RC4 173
  31. The Arithmetic of Public Keys: Modular Arithmetic and the gcd 179
  32. Fermat, Euler and the Totient Function 187
  33. Fast Modular Exponentiation, and Testing a Number for Primality 194
  34. Principles of Public-Key Cryptosystems 202
  35. The RSA Algorithm 208
  36. Why RSA Works, and How It Is Attacked 215
  37. Textbook RSA Is Not RSA: Why Padding Exists 223
  38. Distributing a Public Key: Announcement, Directory, Authority, Certificate 230
  39. Key Management for Secret Keys: Session Keys and the Key Hierarchy 235
  40. Diffie-Hellman Key Exchange 241
  41. The Man in the Middle, and Why Diffie-Hellman Needs Authentication 250
  42. Authentication Requirements: The Attacks on a Message 256
  43. Authentication Functions: Encryption, MAC and Hash 260
  44. Message Authentication Codes 265
  45. Hash Functions: What One Must Do 271
  46. How a Hash Function Is Built, and Breaking a Small One 276
  47. Security of Hash Functions and MACs: The Birthday Attack 282
  48. The Secure Hash Algorithm: SHA-1, SHA-256 and SHA-512 289
  49. HMAC 298
  50. Practical: Writing a Substitution and a Transposition Cipher 306
  51. Practical: Generating and Verifying a Message Authentication Code 311
  52. Practical: A Diffie-Hellman Exchange in Code 316

Module II Digital signatures and authentication, authentication applications, electronic mail security, IP security, web security, intrusion, malicious software and firewalls

  1. Digital Signatures: What One Is and What It Must Do 322
  2. The RSA Digital Signature 327
  3. The ElGamal and Schnorr Signature Schemes 333
  4. Direct and Arbitrated Digital Signatures 340
  5. Authentication Protocols: Mutual Authentication and the Replay Problem 345
  6. One-Way Authentication, and Authentication with a Public Key 352
  7. The Digital Signature Standard, and the DSA Algorithm 357
  8. What the Signature Standard Says Today, and Why DSA Left It 364
  9. Kerberos: The Problem It Solves 371
  10. The Kerberos Dialogue, Step by Step 381
  11. Kerberos Version 5, Realms and What Changed 392
  12. X.509: The Certificate and Its Fields 402
  13. Certificate Chains, Revocation and the CRL 413
  14. Public-Key Infrastructure 425
  15. Pretty Good Privacy: The Five Services 434
  16. How a PGP Message Is Built, and Radix-64 443
  17. PGP Key Management and the Web of Trust 452
  18. S/MIME 460
  19. IP Security: What It Is For 468
  20. The IPsec Architecture: SA, SPD, Transport and Tunnel Mode 475
  21. The Authentication Header 483
  22. Encapsulating Security Payload 490
  23. Combining Security Associations 496
  24. IPsec Key Management: Oakley, ISAKMP and IKE 503
  25. Web Security Considerations 512
  26. SSL: The Architecture and the Record Protocol 518
  27. The SSL and TLS Handshake 525
  28. TLS, and What Each Version Fixed 533
  29. Secure Electronic Transaction, and the Dual Signature 544
  30. Intruders: Who They Are and What They Do 552
  31. Intrusion Techniques: Attacking the Password File 558
  32. Password Selection and Management 564
  33. Intrusion Detection: Statistical Anomaly and Rule-Based 571
  34. Audit Records, Distributed Intrusion Detection and Honeypots 577
  35. Malicious Software: The Taxonomy 584
  36. Viruses: The Four Phases, the Structure and the Types 589
  37. Worms, and the Ones That Made History 596
  38. Virus Countermeasures 603
  39. Denial of Service and Distributed Denial of Service 609
  40. DDoS Countermeasures 615
  41. Firewall Design Principles 621
  42. Types of Firewalls 627
  43. Firewall Configurations, and a Rule Base Read Line by Line 634
  44. Practical: Signing and Verifying a Message in Code 640
  45. Practical: Configuring IPsec, and Reading the Policy Back 645
  46. Practical: Certificates and a Secure Session, End to End 651
  47. Practical: Setting Up an Intrusion Detection System 657
  48. Practical: Analysing a Malware Sample, Safely 662
  49. How the Paper Is Set, and How to Answer It 668
  50. The Comparisons That Cross Both Modules 673
munotes.in

Module I

Introduction, classical encryption, public-key cryptography and RSA, key management, message authentication and hash functions

munotes.in

Chapter One

What Security Means: Confidentiality, Integrity and Availability

Syllabus topic Module 1, "Introduction: Security Trends, The OSI Security Architecture, Security Attacks, Security Services, Security Mechanisms"

In one line

Security means that information and the systems holding it keep doing exactly what their owner intended, and nothing else. Three properties carry that promise: nobody who should not see the data sees it, nobody who should not change the data changes it, and the people who need the data can get it when they need it.

In the wording a student can write in an examination: information security is the protection of information and information systems from unauthorised access, use, disclosure, disruption, modification or destruction, in order to preserve confidentiality, integrity and availability. These three are called the CIA triad, and they are the objectives against which every control in this subject is judged.

Why the subject begins here

Almost nobody attacks a computer for its own sake. They attack it because of what the information inside it can be made to do: a mark that can be raised, a payment that can be redirected, a message that can be read. So the useful question is never "is this system secure", which has no answer, but "which of the three properties does this control protect, and against whom". A student who can name the property under attack can answer half of this paper, because MU's whole Module 1 is built on that vocabulary.

The three properties also give you a way to talk about a loss. When a college result server is defaced, nothing was stolen and nothing was read, so it is not a loss of confidentiality; the marks were altered, so it is a loss of integrity. When the same server is knocked offline on the morning results are published, nothing was read and nothing was altered, so it is a loss of availability alone. Naming the loss correctly is the first step to choosing the control, and it is the thing examiners test.

The three properties, in the standard's own words

Confidentiality. FIPS 199, quoting the United States statute that defines it, is "Preserving authorized restrictions on information access and disclosure, including means for protecting personal privacy and proprietary information". The standard then adds the plain version: "A loss of confidentiality is the unauthorized disclosure of information."

In plain English: only the people who are supposed to see it, see it. Your examination seat number is not confidential, because it is posted on a noticeboard. Your Aadhaar number is. Your marks are confidential from other students and not from you.

Integrity. FIPS 199: "Guarding against improper information modification or destruction, and includes ensuring information non-repudiation and authenticity". A loss of integrity is the unauthorised modification or destruction of information.

In plain English: the data is what it was, and nobody has quietly altered it. Notice that integrity is not about secrecy at all. A public examination timetable has no confidentiality whatever and enormous integrity requirements, because if somebody changes one date, a thousand students arrive on the wrong day.

munotes.in1

What Security Means: Confidentiality, Integrity and Availability

Availability. FIPS 199: "Ensuring timely and reliable access to and use of information". A loss of availability is the disruption of access to or use of information or an information system.

In plain English: it works when you need it. This is the property people forget is part of security, and it is the one attacked most often in practice, because a denial of service attack needs no cleverness at all.

Two more that the standards add, and MU will expect you to know

Confidentiality, integrity and availability are the three the syllabus names, but the security literature adds two, and both appear later in this paper.

Authenticity. RFC 4949 defines authenticity as the property of being genuine and able to be verified and trusted. It is what a digital signature gives you: not only that the message was not altered, but that it came from the person it claims to have come from. It is the reason Module 1 spends five chapters on message authentication.

Accountability. The property that an entity's actions can be traced uniquely to that entity. It is what an audit log gives you, and without it there is no investigation after an incident and no non-repudiation. NIST SP 800-12 Rev. 1 treats both of these as objectives alongside the triad.

Worked example: why you cannot have all three at once

The system. A college publishes semester results on a web server. Three thousand students want them within an hour of publication. The Examination Section wants nobody to see them early and nobody to change them ever.

Step 1: strengthen confidentiality. Require every student to log in with a password, and post nothing publicly. Confidentiality improves. Availability falls, because three thousand simultaneous logins are far heavier than three thousand reads of one static page, and because several hundred students will have forgotten their password on the day.

Step 2: strengthen integrity. Require two officers of the Examination Section to approve every upload, and make the published file read only, signed, and impossible to correct without repeating the approval. Integrity improves. Availability falls again, because a genuine correction to one student's mark now takes two days.

Step 3: strengthen availability. Put the results file on a public content delivery network with no login, cached in fifty places so that no surge can slow it. Availability becomes excellent. Confidentiality is gone, because anyone with the address can read anyone's marks, and integrity is weaker, because there are now fifty copies and no single place to correct.

munotes.in2

What Security Means: Confidentiality, Integrity and Availability

What the example shows. Every one of the three steps was a correct security decision, and every one made another property worse. Security is therefore not a quantity a system has more or less of; it is a set of deliberate trade-offs recorded against a stated requirement. When a question asks you to "design a secure system for X", it is asking which trade-off you chose and why, and an answer that promises all three is a weak answer.

Distinctions that carry marks

ConfidentialityIntegrityAvailability
The promiseonly the right people can read itnobody has altered ityou can get to it when you need it
Attacked byeavesdropping, theft of a database, a careless emailtampering, a virus, a wrong entrya flood of traffic, a cut cable, ransomware
Broken meansdisclosuremodification or destructiondisruption
Typical controlencryption, access controlhash, MAC, digital signature, backupredundancy, capacity, rate limiting
Can you tell it happenedoften notyes, if you checkimmediately, and loudly
SecurityPrivacy
Asksis the data protected from unauthorised accessshould we hold this data at all, and for what
Decided bythe system's designerslaw, and the person the data is about
In Indiathe technical controls in the Information Technology Act 2000 and its rulesthe Digital Personal Data Protection Act 2023
Relationshipyou can have security without privacy, but not privacy without security

What it does not mean

Security is not the same as cryptography. Cryptography is one mechanism, and it serves confidentiality, integrity and authenticity. It does nothing at all for availability, and a great deal of this paper's Module 2 is about controls with no cryptography in them: firewalls, intrusion detection, antivirus.

"Secure" is not a property a system has. It is always relative to a threat and a cost. A lock that resists a student with a hairpin does not resist a locksmith, and the honest statement is always "secure against whom, for how long, at what cost".

Integrity does not mean the data is correct. It means the data has not been altered since it was recorded. If the clerk typed 45 when the answer book said 54, integrity is perfect and the mark is wrong. Integrity protects the record, not the truth of what was recorded.

Availability failures are usually accidents. A power cut, a full disk and a wrong configuration take down more systems than attackers do, which is why availability sits inside security rather than beside it.

Quick revision

  • Security: information and systems do what their owner intended, and nothing else.
  • The CIA triad: confidentiality, integrity, availability.
  • Confidentiality: preserving authorised restrictions on access and disclosure. Lost by disclosure.
  • Integrity: guarding against improper modification or destruction. Lost by modification.
  • Availability: timely and reliable access. Lost by disruption.
  • Two more from the standards: authenticity (genuine and verifiable) and accountability (actions traceable to one entity).
  • The three pull against each other. Strengthening one usually weakens another, and naming the trade-off is the answer.
  • Integrity is about the record being unaltered, not about the record being true.
munotes.in3

What Security Means: Confidentiality, Integrity and Availability

Test yourself

1. Define information security, and name the three properties it preserves. Information security is the protection of information and information systems from unauthorised access, use, disclosure, disruption, modification or destruction. The three properties are confidentiality (only authorised people can read the data), integrity (the data has not been improperly altered or destroyed) and availability (authorised users can reach the data when they need it).

2. A college's public examination timetable is altered so that one paper shows the wrong date. Which property was lost, and which was not? Integrity was lost: the data was modified without authority. Confidentiality was not lost, because the timetable was public and there was nothing to disclose. Availability was not lost either, because the page still answered.

3. Give one control for each of the three properties. Confidentiality: encrypt the data and require a login. Integrity: store a hash or a message authentication code with the record and check it before use. Availability: keep a second server and limit how many requests one address may make.

4. Why is it wrong to call a system "secure" without qualification? Because security is always relative to a threat, a time and a cost. A control that stops a curious classmate will not stop a funded attacker, so the meaningful claim names who the system resists and for how long.

5. A results server is put behind a login, and on results day it collapses under the load. Was that a security failure? Yes. Availability is one of the three properties, so a system that cannot be reached when it is needed has failed a security objective, even though the failure came from a control that was correctly protecting confidentiality. It is the trade-off in the worked example.

6. What do authenticity and accountability add to the triad? Authenticity is the property of being genuine and verifiable, so that a message can be shown to come from the party it claims to; a digital signature provides it. Accountability is the property that an action can be traced uniquely to the entity that performed it; an audit log provides it, and non-repudiation depends on it.

7. Distinguish security from privacy. Security asks whether data is protected from unauthorised access, and is decided by the people who build the system. Privacy asks whether the data should be collected and held at all, and for what purpose, and is decided by law and by the person the data concerns. Security is necessary for privacy but does not by itself produce it.

Contents This chapter on its own page

munotes.in4

Chapter Two

The Security Trend: Why Attacks Got Easier as Attackers Got Less Skilled

Syllabus topic Module 1, "Introduction: Security Trends"

In one line

The tools got packaged. An attack that once needed a specialist now needs a download, so the skill an attacker must have has been falling for forty years while the damage an attacker can do has been rising.

In the wording a student can write in an examination: the security trend is that attack sophistication has increased steadily while the technical knowledge required of the intruder has decreased, because each new attack, once discovered, is written into an automated tool that anybody can run. The consequence is that the population of possible attackers grows much faster than the population of skilled attackers, and defences must therefore assume a large number of unskilled opponents as well as a small number of expert ones.

Why this is the first thing the syllabus says

Because it settles an argument the reader is about to have with themselves. A student meeting this subject usually believes one of two comfortable things: that attackers are rare geniuses, so an ordinary college server is not worth attacking; or that security is a solved problem, because the algorithms are published and everybody uses them. The trend destroys both. Attacks are mostly automated and indiscriminate, and the published algorithms have a working life measured in years, after which they are formally retired.

The practical consequence for a student is the one this whole book is built around: a cryptographic claim without a date is not a claim. "AES is secure" means AES is secure as at the date you are reading, against the attacks published up to then, at the key size you chose. The chapters that follow give a date for every such statement, and so should your examination answers.

The trend, proved by the retirement dates

This table is the evidence. Every date was read off the document named in the last column, and every one of those documents is in this book's authority folder.

What was retiredWhenWhat the document saysSource
DES19 May 2005"was withdrawn on May 19, 2005 and is provided here only for historical purposes"FIPS 46-3 cover sheet
MD5March 2011collision attacks are practical; MD5 must not be used where collision resistance is neededRFC 6151
SHA-1 (new uses)March 2011security considerations restricting SHA-0 and SHA-1RFC 6194
SSL 3.0June 2015"Deprecating Secure Sockets Layer Version 3.0"RFC 7568
Two-key 3DES encryptionMarch 2019"Disallowed"SP 800-131A Rev. 2
Three-key 3DES encryptionend of 2023"Deprecated through 2023"SP 800-131A Rev. 2
TLS 1.0 and TLS 1.1March 2021"Deprecating TLS 1.0 and TLS 1.1"RFC 8996
3DES entirely1 January 2024"TDEA is no longer an approved block cipher"SP 800-67 Rev. 2 withdrawal note
DSA for signing3 February 2024"DSA is no longer approved for digital signature generation"FIPS 186-5, and the FIPS 186-4 withdrawal note
munotes.in5

The Security Trend: Why Attacks Got Easier as Attackers Got Less Skilled

Read the column of dates. Eight of the nine fall after 2010, and four fall after 2019. MU's prescribed textbook was printed in 2017, so a student who learns this subject only from it will state four of these nine facts wrongly, and will say "3DES is secure" and "DSA is the Digital Signature Standard" in an examination that is being sat in 2026. This book teaches what MU sets and then gives the date, in the chapter that owns each one.

What actually drives the trend

Four separate forces push in the same direction, and naming them is usually worth more in an answer than describing the curve.

1. Computing became cheap, and attacks are arithmetic. DES's 56-bit key was a defensible choice in 1977 and an indefensible one in 1998, without anybody discovering anything new about DES. The key did not weaken; the machines got faster. Every key length in this subject is therefore a statement about hardware at a date, which is why NIST publishes a transition schedule rather than a permanent answer.

2. Attacks get packaged. The first person to exploit a weakness needs to understand it. The second thousand need only run the tool the first person published. This is the single mechanism behind the whole trend: the knowledge moves out of the attacker's head and into software.

3. The attack surface grew faster than the defences. In 1980 the systems worth attacking were a few thousand mainframes reached over leased lines. Today a first-year student carries a networked computer, and a college has servers, wireless, cloud accounts and a payment gateway. Every one of those is a place where the three properties of the previous chapter can be lost.

4. Attacking became a business. When the motive was curiosity the volume was limited by the number of curious people. When the motive is money, the volume is limited only by profit, which is why ransomware and credential theft dominate the incident reports rather than clever cryptographic breaks.

Where the subject starts: Anderson, 1980

The earliest document in this book's authority base is James P. Anderson's report of 15 April 1980, Computer Security Threat Monitoring and Surveillance, which is where intrusion detection begins. It is worth knowing for two reasons.

First, it already contains the classification of intruders that Module 2 of this syllabus still uses, and it already argues that you cannot prevent every intrusion, so you must be able to detect one from the records the system keeps anyway. That argument is forty-six years old and is the reason your college's servers keep logs.

munotes.in6

The Security Trend: Why Attacks Got Easier as Attackers Got Less Skilled

Second, it shows what has and has not changed. The threats Anderson lists are the masquerader using someone else's credentials, the legitimate user exceeding their authority, and the user hiding their tracks. Those are exactly the three classes in Module 2's chapter on intruders. The tooling changed beyond recognition; the taxonomy did not.

A worked example: one weakness, three populations

The weakness. In 2014 a flaw was published in the way SSL 3.0 pads a block cipher record, which lets a party who can sit between a browser and a server recover the plaintext one byte at a time. The mathematics is not hard but the setup is fiddly.

Population 1, the week it was published. A handful of people in the world could mount it, because it needed a researcher's understanding and custom code. The number of actual victims was approximately zero.

Population 2, three months later. The attack was in penetration testing toolkits, as a menu item. Anybody running a network scan on a campus could now find every server that still spoke SSL 3.0 and demonstrate the attack in an afternoon, with no understanding of padding at all.

Population 3, June 2015. The IETF published RFC 7568, which prohibits SSL 3.0 outright: implementations must not send or accept it. The weakness did not get harder to exploit. It was removed from the ecosystem instead, by a standards body, eight months after publication.

What the example shows. The trend is not that attackers get cleverer. It is that the distance between discovering a weakness and being able to use it collapses, so the only durable defence is retiring the weak thing. That is why this subject is full of dates, and why every chapter here ends on what the current standard says rather than on what the algorithm was designed to do.

Distinctions that carry marks

VulnerabilityThreatAttackRisk
Isa weakness in the systemsomebody or something that could exploit itan actual attempt to exploit itthe chance of loss, and its size
Examplea server still speaking SSL 3.0anyone on the campus networkthe padding attack being runlikely loss if it succeeds
You canfix itnot remove it, only the exposuredetect and stop itaccept, reduce or transfer it
Passive trendActive trend
Sophistication of attacksrisingrising
Knowledge needed by the attackerfallingfalling
Number of people able to attackrising sharplyrising sharply
Time from publication to mass usefallingfalling

What beginners get wrong here

"Attacks are getting more sophisticated" does not mean "attackers are getting smarter." It means the tools are. The two claims are opposites, and mixing them up is the commonest error in an answer on this topic.

munotes.in7

The Security Trend: Why Attacks Got Easier as Attackers Got Less Skilled

A retired algorithm is not a broken algorithm, and a broken algorithm is not always retired. DES was withdrawn because its key is too short, not because anybody found a flaw in its design. SHA-1 is still permitted for some non-collision uses, such as inside HMAC, long after collisions were found. The right question is always "what property failed, and for which use".

Old does not mean weak. The one-time pad is from 1917 and is unbreakable. AES is from 2001 and, with a 256-bit key, is expected to outlive everybody reading this. Age is not the variable; key length and published cryptanalysis are.

Quick revision

  • The trend: attack sophistication rises while the intruder knowledge required falls, because attacks get packaged into tools.
  • Proved by the retirement dates: DES 2005, MD5 and SHA-1 restricted 2011, SSL 3.0 2015, two-key 3DES disallowed 2019, TLS 1.0 and 1.1 2021, 3DES 2024, DSA for signing 2024.
  • Four drivers: cheap computing, packaged tools, a bigger attack surface, and money.
  • Intrusion detection begins with Anderson, 1980, and his three classes of intruder are still the ones in Module 2.
  • A cryptographic claim with no date is not a claim.
  • Vulnerability is a weakness; threat is who could use it; attack is the attempt; risk is the expected loss.

Test yourself

1. State the security trend in one sentence, and give the mechanism behind it. Attack sophistication has risen steadily while the technical knowledge needed by an intruder has fallen. The mechanism is packaging: once an attack is discovered it is written into an automated tool, so the understanding moves from the attacker into software and the number of people able to run the attack grows enormously.

2. Name three algorithms or protocols that have been formally retired, with their dates. DES, withdrawn 19 May 2005. SSL 3.0, deprecated by RFC 7568 in June 2015. Triple DES, which stopped being an approved block cipher on 1 January 2024 when NIST SP 800-67 Rev. 2 was withdrawn. DSA for signature generation is a fourth, withdrawn by FIPS 186-5 with effect from 3 February 2024.

3. Why was DES withdrawn, and what does that tell you about key lengths generally? Because its 56-bit key became brute-forceable as hardware improved, not because a flaw was found in the cipher. It shows that a key length is a claim about the cost of computing at a date, so every key length recommendation has an expiry and must be read with its date.

4. Distinguish a vulnerability, a threat and an attack. A vulnerability is a weakness in the system, such as an out-of-date protocol still being accepted. A threat is a party or circumstance that could exploit it. An attack is an actual attempt to exploit it. You can remove a vulnerability; you cannot remove a threat, only your exposure to it.

munotes.in8

The Security Trend: Why Attacks Got Easier as Attackers Got Less Skilled

5. Why does this book give a date with every claim about an algorithm's strength? Because strength is relative to published cryptanalysis and to the cost of computing, and both change. A statement such as "SHA-1 is secure" was true in 2000 and false in 2017, so a claim with no date cannot be checked and cannot be correct for long.

6. What is the significance of Anderson's 1980 report? It is where intrusion detection starts. It argues that not every intrusion can be prevented, so systems must detect intrusions from the audit records they already produce, and it classifies intruders into the external masquerader, the legitimate user exceeding authority, and the user who hides their tracks, which is still the classification used in this syllabus.

7. A college server still accepts TLS 1.0. Is that a vulnerability, and what should be done? Yes. RFC 8996 of March 2021 deprecates TLS 1.0 and TLS 1.1, so accepting it is a known weakness that automated scanners will find within minutes of being pointed at the server. The fix is to configure the server to offer TLS 1.2 and TLS 1.3 only, which is exactly the "retire the weak thing" response the trend forces.

Contents This chapter on its own page

munotes.in9

Chapter Three

The OSI Security Architecture: The Words This Subject Is Spoken In

Syllabus topic Module 1, "Introduction: The OSI Security Architecture"

In one line

X.800 is the document that gave this subject its vocabulary: it sorts everything into security attacks, security services and security mechanisms, and almost every question on Module 1's first topic is asking you to place something in one of those three boxes.

In the wording a student can write in an examination: the OSI Security Architecture is ITU-T Recommendation X.800, Security Architecture for Open Systems Interconnection for CCITT Applications, published in March 1991. It defines a systematic way of specifying the security requirements of a system and of characterising the approaches to satisfying them, in terms of security attacks (any action that compromises the security of information), security mechanisms (a process designed to detect, prevent or recover from a security attack) and security services (a processing or communication service that enhances the security of a system, and which makes use of one or more security mechanisms).

Why a dictionary is worth a whole chapter

Because this subject has three words for things that sound alike, and marks are lost on nothing else so often. "Authentication" is a service; "authentication exchange" is a mechanism; "masquerade" is an attack. All three are about identity, and a student who cannot say which box each one goes in cannot answer the question that asks for the mechanisms that support a named service, which MU's own Module 1 topic list sets up directly.

There is a second reason. X.800 is a standard, so its definitions are not one author's opinion. When a question asks you to define non-repudiation, there is a right answer with a citation behind it, and that is worth more in an answer than a paraphrase. This book therefore quotes X.800 where X.800 is the authority, and says so.

The three headings, and how they fit together

Think of a single sentence with three slots. An attack threatens something. A service is the promise you make to defend against it. A mechanism is the machinery that keeps the promise.

HeadingX.800's ideaA concrete instance
Security attackany action that compromises the security of information owned by an organisationsomebody reads your marks off the wire
Security servicea service that counters security attacks, and makes use of one or more security mechanismsdata confidentiality
Security mechanisma process designed to detect, prevent or recover from a security attackencipherment

The arrow runs attack, then service, then mechanism, and it runs that way for a reason: you do not choose a mechanism first. You identify what can go wrong, decide what you will promise, and only then pick the machinery. An answer that starts from the mechanism ("we will use AES") has skipped the two steps the marks are in.

munotes.in10

The OSI Security Architecture: The Words This Subject Is Spoken In

The vocabulary, defined once for the whole book

Each of these is used in every later chapter and is not redefined there.

Threat. X.800 distinguishes two kinds, and the distinction is the basis of the next chapter. A passive threat is, in the Recommendation's own words, "The threat of unauthorized disclosure of information without changing the state of the system." An active threat is "The threat of a deliberate unauthorized change to the state of the system", and X.800's note gives the examples: modification of messages, replay of messages, insertion of spurious messages, masquerading as an authorized entity, and denial of service.

Vulnerability. A weakness in a system that a threat can exploit. A threat is who or what might act; a vulnerability is the hole they would act through. A system with a vulnerability and no threat is safe by luck, and a system with a threat and no vulnerability is safe by design.

Attack. An assault on system security that derives from a threat: an actual attempt to evade security services and violate the security policy of a system. A threat is a possibility; an attack is an event.

Risk. The expectation of loss: how likely a successful attack is, multiplied by what it would cost. Risk is the quantity a manager acts on, and it is the reason perfect security is never the goal.

Security policy. The set of rules that says what is permitted and what is not. Every mechanism in this book enforces a policy, and a mechanism without a stated policy cannot be judged right or wrong.

Entity, principal, subject and object. X.800 speaks of entities communicating. An entity that can be authenticated is a principal. In access control the thing that acts is the subject and the thing acted upon is the object: a student is a subject, a mark sheet is an object.

Encipherment. X.800's word for what everybody else calls encryption, and the Recommendation is careful about it: encipherment algorithms "may be reversible or irreversible", and the reversible ones divide into symmetric, where "knowledge of the encipherment key implies knowledge of the decipherment key and vice versa", and asymmetric, where it does not. That one sentence is the whole of Module 1 in miniature.

A worked example: placing eight things in the right box

The scenario. A college runs a student portal. The Examination Section is worried about eight things. Sort them.

What they saidWhich boxWhy
"Somebody could read the marks as they cross the campus wireless"attack (passive)information disclosed, system unchanged
"We must be sure only the clerk can upload marks"service: authentication and access controlit is a promise, not machinery
"We will make them log in with a one-time code"mechanism: authentication exchangethe machinery that keeps the promise
"Somebody could pretend to be the clerk"attack (active), specifically masqueradeX.800: "The pretence by an entity to be a different entity"
"The clerk must not be able to deny having uploaded"service: non-repudiationagain a promise
"We will make the clerk sign each upload"mechanism: digital signaturethe machinery
"Our old server accepts TLS 1.0"vulnerabilitya weakness, not an action
"Results day traffic could take the site down"attack (active): denial of servicea change to the state of the system
munotes.in11

The OSI Security Architecture: The Words This Subject Is Spoken In

The step that carries the marks. Notice that rows 2 and 3 are the same concern stated twice, once as a service and once as a mechanism, and rows 5 and 6 likewise. That pairing is exactly what MU's Module 1 asks for when it lists "Security Services" and "Security Mechanisms" as separate topics, and it is the thing to practise: given a service, name the mechanisms; given a mechanism, name the services it supports.

The model X.800 gives you to hang everything on

Every protocol in this book is an instance of one picture, and it is worth fixing now because Module 2 is nothing but instances of it.

Two parties want to exchange a message across a channel they do not control. Each applies a security-related transformation to what they send, using secret information that the opponent does not have. Often a trusted third party is needed, either to distribute the secret information to both parties before they start, or to arbitrate afterwards if they disagree. The opponent sits on the channel.

Four tasks follow from that picture, and they are the four things the rest of this book does.

  1. Design the transformation. Chapters on DES, AES, RSA, the hash functions.
  2. Generate the secret information the transformation needs. The chapters on key generation.
  3. Distribute and share the secret information. Diffie-Hellman, the key distribution centre, certificates, Kerberos.
  4. Specify a protocol that uses both, so that the service is actually delivered. IPsec, SSL and TLS, PGP, S/MIME.

There is a second model for a different problem, and MU's Module 2 is built on it: network access security, where the threat is not somebody reading the channel but somebody or something getting inside. Its two answers are a gatekeeper function, which is the firewall chapters, and internal controls, which is the intrusion detection and antivirus chapters.

What beginners get wrong here

X.800 is not the OSI seven-layer model. It is a security architecture written to sit alongside that model, and its three headings are useful whether or not anybody uses OSI layering. The seven layers are not examinable here; the three headings are.

munotes.in12

The OSI Security Architecture: The Words This Subject Is Spoken In

A service is not a mechanism, even when only one mechanism can provide it. Non-repudiation is a service; a digital signature is the mechanism. Writing "non-repudiation is a digital signature" loses the mark because the question is testing whether you know the two are different kinds of thing.

Encipherment is a mechanism, not a service. It is the commonest slip in this topic. Confidentiality is the service; encipherment is one mechanism that provides it, and there are others, such as routing control and traffic padding.

X.800 is a 1991 document, and some of it has dated. The taxonomy has not, which is why MU still sets it, but its examples and its layer placements are of their time. This book uses it for the vocabulary and takes the current detail from the standards that came later.

Quick revision

  • The OSI Security Architecture is ITU-T Recommendation X.800, March 1991.
  • Three headings: security attack, security service, security mechanism. The order matters.
  • Attack: any action that compromises security. Service: a promise that counters attacks, delivered by mechanisms. Mechanism: the machinery that detects, prevents or recovers.
  • Passive threat: unauthorised disclosure without changing the state of the system. Active threat: a deliberate unauthorised change to the state of the system.
  • Active examples in X.800's own note: modification, replay, insertion of spurious messages, masquerade, denial of service.
  • Threat is what might happen, vulnerability is the weakness it would use, attack is the attempt, risk is likelihood times loss.
  • The model: two parties, a transformation, secret information, sometimes a trusted third party, an opponent on the channel. Four tasks follow.
  • Encipherment is a mechanism; confidentiality is the service.

Test yourself

1. What is the OSI Security Architecture, and what are its three headings? It is ITU-T Recommendation X.800 of March 1991, a systematic way of specifying security requirements and characterising the approaches that satisfy them. Its three headings are security attacks, security services and security mechanisms.

2. Define a security service and a security mechanism, and give the relationship between them. A security service is a processing or communication service that enhances the security of a system and counters security attacks. A security mechanism is a process designed to detect, prevent or recover from a security attack. A service is a promise; it is delivered by one or more mechanisms, and one mechanism can support several services.

3. Distinguish a passive threat from an active threat, in X.800's terms. A passive threat is the threat of unauthorised disclosure of information without changing the state of the system. An active threat is the threat of a deliberate unauthorised change to the state of the system, with examples of modification of messages, replay, insertion of spurious messages, masquerading and denial of service.

munotes.in13

The OSI Security Architecture: The Words This Subject Is Spoken In

4. Distinguish threat, vulnerability, attack and risk. A threat is a potential for a security violation. A vulnerability is a weakness through which a threat could act. An attack is an actual attempt to violate the security policy. Risk is the expected loss, being the likelihood of a successful attack multiplied by its cost.

5. Is encipherment a service or a mechanism? What is the service it supports? It is a mechanism. It supports data confidentiality principally, and also contributes to data integrity, authentication and non-repudiation when combined with other mechanisms.

6. Draw out the model of network security and name the four tasks it implies. Two parties exchange a message over an insecure channel, each applying a security-related transformation that uses secret information, with a trusted third party where one is needed, and an opponent on the channel. The four tasks are: design the transformation; generate the secret information; distribute and share it; and specify a protocol that uses both to deliver the service.

7. Give the two elements of the network access security model, and name the part of this syllabus that treats each. A gatekeeper function, which is the firewalls topic of Module 2; and internal controls, which are the intrusion detection and malicious software topics of Module 2.

Contents This chapter on its own page

munotes.in14

Chapter Four

Security Attacks: Passive and Active

Syllabus topic Module 1, "Introduction: Security Attacks"

In one line

A passive attack reads and changes nothing. An active attack changes something. That single difference decides everything else: what you can hope to do about it, and which of the two things you spend your money on.

In the wording a student can write in an examination: security attacks are classified as passive or active. A passive attack attempts to learn or make use of information from the system but does not affect system resources; X.800 defines the corresponding threat as "The threat of unauthorized disclosure of information without changing the state of the system." An active attack attempts to alter system resources or affect their operation; X.800 defines it as "The threat of a deliberate unauthorized change to the state of the system."

Why the classification matters more than the list

Because the two halves need opposite strategies, and a question that asks you to "discuss security attacks" is asking for that, not for six paragraphs of description.

A passive attack leaves no trace. If somebody copies your marks off the campus wireless, nothing on your side changes: the message still arrives, on time, unaltered. There is nothing to notice, so detection is not available to you and prevention is the only option. Prevention, for a passive attack, essentially means encryption.

An active attack necessarily leaves a trace, because something changed. But it cannot be prevented absolutely, because the attacker has a huge number of possible targets and you would have to defend every physical link, every wireless hop and every router between the two parties. So the strategy is to detect the attack and to recover from it, which is why Module 2 spends whole chapters on intrusion detection and on recovery.

That gives you the shape of the answer: passive attacks, prevent, by encryption. Active attacks, detect and recover, by authentication, integrity checks, logging and backups.

Passive attacks: the two kinds

Release of message contents. The straightforward one. A telephone conversation, an electronic mail message, a transferred file contains information the sender would rather not have read, and the opponent reads it.

A concrete example: a student logs in to a college portal over the campus wireless, which uses a shared password everybody in the hostel knows. Anybody on the same network, running freely available capture software, sees the login and everything after it, unless the portal uses HTTPS. Nothing about the student's session looks different. The portal's own logs show one ordinary login.

Traffic analysis. The subtle one, and the one questions like. Suppose the messages are encrypted, so the opponent cannot read them. They can still see that the messages were sent, how big they were, how often they went, and who was talking to whom. From that alone a great deal follows.

munotes.in15

Security Attacks: Passive and Active

A concrete example: the Examination Section's server normally exchanges a few small messages an hour with the printer's office. On one night in November it exchanges two hundred megabytes with them at three in the morning. Nobody has read a single mark, and everybody now knows the question papers went out that night. Or, on a personal scale: an opponent who sees encrypted traffic between a student's phone and a particular clinic's booking system learns something private without decrypting one byte.

Traffic analysis is why X.800 lists traffic flow confidentiality as a service of its own and traffic padding as a mechanism of its own. Encrypting the payload does not defeat it; only sending cover traffic does.

Active attacks: the four kinds

X.800's own note on active threats names the examples: "modification of messages, replay of messages, insertion of spurious messages, masquerading as an authorized entity and denial of service". The four standard headings are these.

Masquerade. X.800: "The pretence by an entity to be a different entity." One entity pretends to be a different one, usually by replaying captured authentication information so that the receiver believes it is talking to somebody it is not.

Example: an attacker captures a clerk's login from the wireless in the afternoon and replays it that evening, and the portal accepts an upload of marks as though the clerk had sent it. Masquerade usually comes with one of the other three: the captured credential is a replay, and the upload is an insertion.

Replay. The passive capture of a data unit and its subsequent retransmission to produce an unauthorised effect. The message is genuine, which is what makes it dangerous: every signature on it is valid, every checksum correct. It is simply being sent again, or later, or to somebody else.

Example: a student's fee payment authorisation is captured and sent to the payment gateway three more times. Nothing in the message is forged. Nothing in the message is wrong. The student has paid four times. Defeating replay needs a nonce, a sequence number or a timestamp, and that is why Module 2's chapter on authentication protocols exists.

Modification of messages. Some portion of a legitimate message is altered, or messages are delayed or reordered, to produce an unauthorised effect. Altering "Allow Ramesh Patil to read the accounts file" to "Allow Suresh Kadam to read the accounts file" is the classic instance.

Example on this campus: an internal mark of 12 is changed to 42 in transit between the department and the Examination Section. Notice that encryption does not stop this by itself. A block cipher in the wrong mode is quite happy to have its ciphertext altered, and the chapter on cipher block chaining shows exactly what an attacker gets by flipping one ciphertext bit. Integrity needs a hash or a message authentication code, not merely secrecy.

munotes.in16

Security Attacks: Passive and Active

Denial of service. The prevention or inhibition of the normal use or management of communication facilities. It may have a specific target, such as suppressing all messages to a particular server, or it may be a general disruption, such as flooding a network so that nothing gets through.

Example: on the morning results are published, a few thousand requests a second arrive at the college's results page from machines all over the world, and no student can reach it for two hours. Nothing was read and nothing was altered. The only property lost was availability, and it was lost completely. Module 2's chapter on distributed denial of service works out the arithmetic of how this is done with very little bandwidth of the attacker's own.

A worked example: one interception, four outcomes

The setup. Asha, in the accounts office, sends a message to the bank: Pay 40,000 to vendor 8817. Darth sits on the network between them and captures it. What Darth chooses next decides which attack this is.

Darth's choiceThe attackWhat Asha and the bank seeWhat stops it
Reads it, forwards it unchangedpassive, release of message contentsnothing at allencryption
Does not read it but notes that a payment message went to that bank at that hourpassive, traffic analysisnothing at alltraffic padding, cover traffic
Sends the same message twice moreactive, replaythe bank pays three timesnonce, sequence number or timestamp
Changes 8817 to 8819active, modificationthe bank pays the wrong vendormessage authentication code or signature
Sends the message as though from Asha, having never seen a real oneactive, masqueradethe bank pays a vendor Asha never namedauthentication
Floods the bank's gateway so the message never arrivesactive, denial of servicethe payment silently failsrate limiting, capacity, filtering

The step that carries the marks. One captured message and six different attacks. That is the answer to "classify security attacks with examples": not six unrelated stories, but one interception and the six things an opponent can do with it, with the countermeasure for each.

Distinctions that carry marks

Passive attackActive attack
Does it change the systemnoyes
X.800's definitionunauthorised disclosure without changing the state of the systema deliberate unauthorised change to the state of the system
Kindsrelease of message contents, traffic analysismasquerade, replay, modification, denial of service
Can you detect itnoyes, in principle
Can you prevent ityes, by encryptionnot absolutely
The strategypreventiondetection and recovery
Property attackedconfidentialityintegrity or availability
munotes.in17

Security Attacks: Passive and Active

InterruptionInterceptionModificationFabrication
Attacksavailabilityconfidentialityintegrityauthenticity
Kindactivepassiveactiveactive
Exampledenial of serviceeavesdroppingaltering a paymenta forged message

The second table is a different cut of the same ground, by which property is attacked rather than by whether the system changed. Both are asked, so both are worth knowing; they are not two different taxonomies to be confused, they are two ways of sorting the same six attacks.

What beginners get wrong here

"Passive" does not mean harmless. It means the attacker does not change anything. Reading every mark in a university is entirely passive and is the most damaging thing on this page.

Encryption does not stop an active attack. It stops the passive ones. A ciphertext can be replayed, reordered, truncated or flipped, and a cipher on its own says nothing about whether it arrived as sent. That separation, confidentiality against integrity, is the reason message authentication has its own five chapters.

Traffic analysis is not a failure of encryption. It works precisely because the payload is encrypted and the opponent has to fall back on the metadata. It is defeated by padding and cover traffic, not by a stronger cipher.

Denial of service is a security attack. Students leave it out because nothing is stolen. Availability is one of the three properties, so a successful denial of service is a complete security failure.

Quick revision

  • Passive: reads, changes nothing. Release of message contents, traffic analysis. Prevent (encryption); you cannot detect it.
  • Active: changes something. Masquerade, replay, modification of messages, denial of service. Detect and recover; you cannot prevent it absolutely.
  • X.800 passive threat: unauthorised disclosure without changing the state of the system.
  • X.800 active threat: a deliberate unauthorised change to the state of the system.
  • Masquerade, in X.800's words: the pretence by an entity to be a different entity.
  • Replay uses a genuine message, so every signature on it is valid: the defence is a nonce, a sequence number or a timestamp.
  • The second cut: interruption (availability), interception (confidentiality), modification (integrity), fabrication (authenticity).
  • Traffic analysis survives encryption; traffic padding is the answer.

Test yourself

1. Classify security attacks and give the defining difference. Into passive and active. A passive attack learns or makes use of information from the system without affecting system resources; an active attack alters system resources or affects their operation. The difference is whether the state of the system changes.

2. Name the two passive attacks and explain why neither can be detected. Release of message contents and traffic analysis. Neither alters the message or the system, so nothing on the legitimate parties' side is different from a normal exchange and there is no evidence to detect. The defence is therefore prevention, by encryption.

munotes.in18

Security Attacks: Passive and Active

3. What is traffic analysis, and why does encryption not stop it? Observing the pattern of encrypted traffic (who talks to whom, when, how often, how much) and drawing conclusions from it. Encryption hides the contents but not the existence, size, timing or endpoints of a message, so the pattern remains readable. Traffic padding and cover traffic are the countermeasures.

4. Name the four active attacks, with one example each. Masquerade: an attacker replays a clerk's captured login and uploads marks. Replay: a captured payment authorisation is sent three more times. Modification of messages: a mark of 12 is altered to 42 in transit. Denial of service: the results page is flooded on results day so no student can reach it.

5. Why can active attacks not be prevented absolutely? Because the attacker has a very large number of possible points of attack, physical and logical, across every link and device between the parties, and defending all of them completely is not feasible. The realistic goal is to detect the attack, recover from any disruption it causes, and make it expensive.

6. Why does replay work even when every message is signed? Because the replayed message is genuine. Its signature is valid and its contents are unaltered; the only thing wrong is that it is being delivered again or at the wrong time. Freshness must therefore be supplied separately, by a nonce, a sequence number or a timestamp.

7. Sort interruption, interception, modification and fabrication by the property each attacks, and say which is passive. Interruption attacks availability; interception attacks confidentiality; modification attacks integrity; fabrication attacks authenticity. Only interception is passive; the other three are active.

Contents This chapter on its own page

munotes.in19

Chapter Five

Security Services: The Five Things Security Promises

Syllabus topic Module 1, "Introduction: Security Services"

In one line

A security service is a promise: confidentiality, integrity, authentication, access control and non-repudiation. Each one is a promise about something an attack from the previous chapter would break, and each is delivered by machinery from the next one.

In the wording a student can write in an examination: a security service is a processing or communication service provided by a system to give a specific kind of protection to system resources. X.800 defines five categories: authentication, access control, data confidentiality, data integrity and non-repudiation, and divides most of them into sub-services.

Why there are five, and not four or six

Because each one answers a different question, and the questions do not overlap.

  • Who are you? Authentication.
  • Are you allowed to do that? Access control.
  • Can anybody else read it? Data confidentiality.
  • Did it arrive as it was sent? Data integrity.
  • Can you later deny having sent it? Non-repudiation.

Students often try to reduce the list, and it will not reduce. Authentication without access control identifies a person and then lets them do anything. Access control without authentication enforces rules against a name anybody can claim. Integrity without authentication tells you the message is unaltered without telling you who wrote it. Each pair is a different failure, and the examination asks about the pairs.

The five, with X.800's own sub-services

1. Authentication

The assurance that the communicating entity is the one it claims to be. X.800 divides it in two, and the division is asked.

Peer entity authentication (X.800 clause 5.2.1.1) gives confidence in the identity of the entity at the other end of a connection, at the time the connection is set up. It protects against masquerade and against replay of a whole previous connection.

Data origin authentication (clause 5.2.1.2) gives confidence that the source of a data unit is the one claimed. It applies to a single message, not a connection, so it suits electronic mail, where there is no connection to authenticate: the message arrives hours later and the sender is gone.

The difference matters because data origin authentication by itself provides no protection against duplication or modification of the data unit. A signed email can be sent to you twice, and both copies are properly signed. Peer entity authentication belongs to a live connection, where sequencing can be enforced; data origin authentication belongs to a standalone message, where it cannot.

2. Access control

The prevention of unauthorised use of a resource: who may use what, under what conditions, and for what operations. Authentication answers "who are you"; access control answers "and what may you do". It is the service every operating system, database and college portal implements, and it presupposes authentication, because a rule about a student is worthless if anybody can claim to be that student.

munotes.in20

Security Services: The Five Things Security Promises

3. Data confidentiality

The protection of data from unauthorised disclosure. X.800 gives four sub-services, and this is the clause worth memorising because all four appear in later chapters.

Connection confidentiality (5.2.3.1): all user data on a connection is protected. This is what TLS gives a browser.

Connectionless confidentiality (5.2.3.2): the user data in a single data unit is protected. This is what an encrypted email or a single IPsec-protected datagram gives.

Selective field confidentiality (5.2.3.3): only chosen fields are protected. A form might encrypt the card number and leave the customer's name in clear, which is exactly what the dual signature in the Secure Electronic Transaction chapter does.

Traffic flow confidentiality (5.2.3.4): the protection of the information that could be derived from observing traffic flows. This is the service that answers traffic analysis, and it is the one a cipher alone does not provide.

4. Data integrity

The assurance that data is received exactly as sent, with no duplication, insertion, modification, reordering or replay. X.800 gives five sub-services, and the first two carry the distinction that is set most often.

Connection integrity with recovery (5.2.4.1): the integrity of all user data on a connection is protected, and any modification, insertion, deletion or replay is detected with recovery attempted.

Connection integrity without recovery (5.2.4.2): the same detection, but only reporting, with no attempt to recover.

Selective field connection integrity (5.2.4.3): integrity of chosen fields within the user data of a connection.

Connectionless integrity (5.2.4.4): integrity of a single data unit, which may also take a limited form of detection of modification.

Selective field connectionless integrity (5.2.4.5): integrity of chosen fields within a single data unit.

Why would anyone choose the version without recovery? Because recovery costs something, and at the wrong layer it costs too much. A protocol that must detect and repair every altered datagram carries state, buffers and retransmission logic; one that merely reports the fault and drops the connection is far simpler, and the application above it can decide what to do. Detection alone is often the right engineering answer, which is why the standard offers both.

5. Non-repudiation

Protection against one of the parties to a communication denying that they took part. X.800 gives two forms.

Non-repudiation with proof of origin (5.2.5.1): the recipient is given proof of the origin of the data, so the sender cannot later deny having sent it.

Non-repudiation with proof of delivery (5.2.5.2): the sender is given proof of delivery, so the recipient cannot later deny having received it.

This is the service that needs a public key. That claim is the whole of the worked example below, and it is the most commonly missed point in the topic.

munotes.in21

Security Services: The Five Things Security Promises

A worked example: why a MAC cannot give non-repudiation

The setup. Asha and Bharat share one secret key, K. Asha sends Pay 40,000 to vendor 8817 with a message authentication code computed over it using K. A message authentication code, or MAC, is a short tag computed from the message and a shared secret key; anybody holding the key can recompute it and check it.

Step 1: integrity and authentication both succeed. Bharat recomputes the tag with K and it matches. He knows the message was not altered, because an attacker without K cannot produce a matching tag for a changed message. He knows it came from somebody holding K, and only he and Asha hold it. So he has data integrity and data origin authentication.

Step 2: the dispute. Asha later denies sending it. Bharat produces the message and the tag.

Step 3: why Bharat loses. Asha's answer is: "Bharat holds K too. He could have written that message himself and computed that tag himself. There is nothing in that tag that only I could have produced." And she is right. A MAC is symmetric: everybody who can verify it can also forge it. Bharat cannot prove to a third party, a court or an arbitrator, that the message came from Asha rather than from himself.

Step 4: what fixes it. Asha signs the message with her private key, which nobody else has, and Bharat verifies with her public key, which everybody has. Now the verification succeeds for anybody who cares to check, and only Asha could have produced the signature. Bharat can prove origin to a third party. That is non-repudiation with proof of origin.

The step that carries the marks. The reason is the asymmetry, not the strength. A MAC with a 256-bit key is enormously strong and still cannot give non-repudiation, because the verifier and the signer are the same role. A signature with a much smaller security margin can, because they are different roles.

In Indian law. This is not only a technical distinction. Section 3A of the Information Technology Act 2000 recognises an electronic signature, and section 5 provides that where any law requires a signature, that requirement is satisfied by an electronic signature affixed in the prescribed manner. The Act therefore builds on exactly the asymmetry above: a signature is attributable to the person whose private key made it.

Distinctions that carry marks

AuthenticationAccess controlNon-repudiation
Answerswho are youwhat may you docan you deny it later
Needsa credentiala policy and an authenticated identitya private key
Attack counteredmasqueradeunauthorised usea party lying afterwards
Mechanismauthentication exchange, signatureaccess control lists, capabilitiesdigital signature, notarisation
munotes.in22

Security Services: The Five Things Security Promises

Peer entity authenticationData origin authentication
Applies toa connection, at set-upone data unit
Suitsa live session, such as TLSelectronic mail, such as PGP
Protects against duplicationwith the connection's sequencing, yesno
Data integrityData confidentiality
Promiseit arrived as sentnobody else read it
Broken bymodification, replay, reorderingdisclosure
Mechanismhash, MAC, signatureencipherment
Independent?yes: you can have either without the other

What beginners get wrong here

Integrity is not a weaker form of confidentiality. They are independent. A public timetable needs integrity and no confidentiality. A stored password needs confidentiality and, oddly, rather little integrity, because a corrupted password simply fails to match.

Authentication does not include authorisation. Logging in proves who you are. Whether you may see the accounts file is access control, and a system that confuses the two gives every authenticated user everything.

A MAC gives authentication but not non-repudiation. The worked example above is the whole answer, and it is set.

Availability is not one of X.800's five services. It appears as one of the three properties of the first chapter, and the Recommendation treats it through other means, but if a question asks for X.800's security services the answer is the five named here. Some texts add availability as a sixth; say which list you are giving.

Quick revision

  • Five services: authentication, access control, data confidentiality, data integrity, non-repudiation.
  • Authentication: peer entity (a connection, at set-up) and data origin (one data unit, no protection against duplication).
  • Confidentiality: connection, connectionless, selective field, traffic flow.
  • Integrity: connection with recovery, connection without recovery, selective field connection, connectionless, selective field connectionless.
  • Non-repudiation: with proof of origin, with proof of delivery.
  • A MAC cannot give non-repudiation, because the verifier can also forge. A digital signature can, because only the holder of the private key can sign.
  • In India, sections 3A and 5 of the Information Technology Act 2000 give an electronic signature the effect of a signature.
  • Integrity and confidentiality are independent; authentication and access control are different services.

Test yourself

1. List X.800's five security services and say what each one promises. Authentication (the other party is who it claims to be), access control (only permitted use of a resource), data confidentiality (no unauthorised disclosure), data integrity (the data arrived exactly as sent) and non-repudiation (neither party can later deny taking part).

2. Distinguish peer entity authentication from data origin authentication. Peer entity authentication confirms the identity of the entity at the other end of a connection, at the time the connection is established, and is used in a live session. Data origin authentication confirms the source of a single data unit and is used for store-and-forward traffic such as electronic mail; by itself it gives no protection against duplication or modification of the data unit.

munotes.in23

Security Services: The Five Things Security Promises

3. Name X.800's four confidentiality sub-services. Connection confidentiality, connectionless confidentiality, selective field confidentiality, and traffic flow confidentiality.

4. What is the difference between connection integrity with recovery and without recovery, and why would anybody choose the weaker one? Both detect modification, insertion, deletion and replay on a connection; the first attempts to recover from what it detects and the second only reports it. The reporting form is chosen because recovery costs state, buffering and retransmission, and at the wrong layer that cost is not worth paying: the application above can decide what to do.

5. Why can a message authentication code not provide non-repudiation? Because the key is shared. Anybody who can verify the tag can also compute it, so the recipient could have produced the message and the tag themselves. There is nothing in the tag that only the sender could have made, so origin cannot be proved to a third party. A digital signature made with a private key can be verified by anybody and produced by only one party, which is what non-repudiation requires.

6. Give the two forms of non-repudiation. Non-repudiation with proof of origin, which stops the sender denying that they sent the data; and non-repudiation with proof of delivery, which stops the recipient denying that they received it.

7. Which service answers traffic analysis, and why is encryption alone not enough? Traffic flow confidentiality. Encryption conceals the contents but leaves the existence, length, frequency and endpoints of messages visible, and conclusions can be drawn from those alone. The service is provided by traffic padding and cover traffic, not by a stronger cipher.

Contents This chapter on its own page

munotes.in24

Chapter Six

Security Mechanisms, and Which Service Each One Serves

Syllabus topic Module 1, "Introduction: Security Mechanisms"

In one line

A mechanism is the machinery. X.800 gives eight specific ones (encipherment, digital signature, access control, data integrity, authentication exchange, traffic padding, routing control, notarisation) and five pervasive ones that are not tied to any single service.

In the wording a student can write in an examination: a security mechanism is a process, or a device incorporating such a process, designed to detect, prevent or recover from a security attack. X.800 divides mechanisms into specific security mechanisms, which may be incorporated into an appropriate protocol layer to provide some of the security services, and pervasive security mechanisms, which are not specific to any particular service or layer.

The eight specific mechanisms

1. Encipherment. The use of mathematical algorithms to transform data into a form that is not readily intelligible. X.800 notes that the transformation may be reversible or irreversible, and that reversible encipherment is either symmetric, where "knowledge of the encipherment key implies knowledge of the decipherment key and vice versa", or asymmetric, where it does not. The irreversible kind is what we now call a hash function.

2. Digital signature. Data appended to, or a cryptographic transformation of, a data unit that allows the recipient to prove the source and the integrity of the data unit, and to protect against forgery. Two properties matter: only the signer can produce it, and anybody can verify it.

3. Access control. A variety of mechanisms that enforce access rights to resources: access control lists, capabilities, security labels, times of access, routes of access, and duration of access.

4. Data integrity. A variety of mechanisms used to assure the integrity of a data unit or a stream of data units. X.800 is careful to distinguish integrity of a single unit, which needs a check value, from integrity of a sequence, which additionally needs a sequence number or a timestamp.

5. Authentication exchange. A mechanism intended to ensure the identity of an entity by means of an information exchange. X.800 lists the techniques it may use: time stamping and synchronised clocks, two-way and three-way handshakes for unilateral and mutual authentication respectively, and non-repudiation services achieved by signature or notarisation.

6. Traffic padding. The insertion of bits into gaps in a data stream to frustrate traffic analysis attempts. It is effective only if the padding is itself enciphered, otherwise the opponent can simply tell padding from data.

7. Routing control. Enables the selection of particular physically secure routes for certain data, and allows routing changes, especially when a breach of security is suspected. It is the mechanism most often left out of an answer, and it is one of only three that X.800 marks as providing traffic flow confidentiality.

munotes.in25

Security Mechanisms, and Which Service Each One Serves

8. Notarisation. X.800 defines it as "The registration of data with a trusted third party" that allows the later assurance of the accuracy of its characteristics such as content, origin, time and delivery. It is how a third party makes a claim about a message stick, and it is what Module 2's arbitrated digital signatures and the whole certificate hierarchy are built on.

The five pervasive mechanisms

These are not tied to a particular service or layer, and they are the ones that make the rest workable.

Trusted functionality. That which is judged to be correct with respect to some criteria. Everything else rests on it: if the code implementing your cipher is untrustworthy, the cipher is irrelevant.

Security labels. A marking bound to a resource that names its security attributes, which is how a system can enforce a policy about data rather than about users.

Event detection. Detection of security-relevant events, which is the mechanism intrusion detection is built out of.

Security audit trail. Data collected and potentially used to facilitate a security audit, which is an independent review of system records. Without it there is no accountability and no investigation.

Security recovery. Deals with requests from mechanisms such as event handling and management functions, and takes recovery actions. This is the "recover" in the definition of a mechanism, and it is the one students forget is part of security at all.

X.800's own table, service by mechanism

This is Table 1 of the Recommendation. A mark means the mechanism is considered appropriate for that service, on its own or with others; a blank means it is considered not appropriate. The Recommendation adds a note that in some instances a mechanism provides more than the service needs but could nevertheless be used.

ServiceEnciph.SignatureAccess ctlIntegrityAuth. exch.PaddingRoutingNotarisn
Peer entity authenticationyesyesyes
Data origin authenticationyesyes
Access control serviceyes
Connection confidentialityyesyes
Connectionless confidentialityyesyes
Selective field confidentialityyes
Traffic flow confidentialityyesyesyes
Connection integrity with recoveryyesyes
Connection integrity without recoveryyesyes
Selective field connection integrityyesyes
Connectionless integrityyesyesyes
Selective field connectionless integrityyesyesyes
Non-repudiation, originyesyesyes
Non-repudiation, deliveryyesyesyes

Four things to read out of it, because these are what a question on the table is testing.

Encipherment appears against everything except the two non-repudiation rows and access control. It is the most widely useful mechanism in the standard, and that is why Module 1 is mostly about it.

A digital signature appears against no confidentiality row at all. Signing does not hide. This is the cell most often filled in wrongly, and getting it right is worth a mark on its own.

munotes.in26

Security Mechanisms, and Which Service Each One Serves

Non-repudiation is the only service for which encipherment is not appropriate. Its three mechanisms are digital signature, data integrity and notarisation, which is the previous chapter's worked example expressed as a row of a table.

Traffic flow confidentiality is the only service with three mechanisms from three different families: encipherment, traffic padding and routing control. It is the service you cannot buy with a cipher alone.

One honest note about the source. The text layer of the ITU PDF merges a cell in the "Connection confidentiality" row, printing seven marks where there should be eight. The row above and the row below are intact and identical in shape, and the connectionless row reads encipherment and routing control, so the connection row has been reconstructed the same way. That is stated here rather than presented as if it had been read cleanly.

A worked example: designing for one requirement

The requirement. The Examination Section says: "A department must be able to submit internal marks to us over the campus network. We must know which department sent them, nobody else on the network may read them, we must be certain they were not altered, and if a department later denies submitting them we must be able to prove it."

Step 1: name the services. Four sentences, four services. "Which department sent them" is data origin authentication. "Nobody else may read them" is connectionless confidentiality, connectionless because each submission is a standalone message. "Not altered" is connectionless integrity. "Prove it against a denial" is non-repudiation with proof of origin.

Step 2: read the mechanisms off the table. Data origin authentication takes encipherment or a digital signature. Connectionless confidentiality takes encipherment, or routing control. Connectionless integrity takes encipherment, a digital signature, or a data integrity mechanism. Non-repudiation of origin takes a digital signature, a data integrity mechanism, or notarisation, and not encipherment.

Step 3: choose the smallest set that covers all four. A digital signature covers three of the four rows. Encipherment covers the fourth, confidentiality, which the signature cannot. So the answer is: hash the submission, sign the hash with the department's private key, and encrypt the whole thing. Two mechanisms, four services.

Step 4: notice what the table forced. Nothing in this design was chosen by taste. The requirement named four services, the standard's table named the mechanisms that serve them, and the intersection gave the design. That is the reasoning a question asking you to "design a scheme for the following requirement" is looking for.

The two models the rest of the book fills in

The communication model. Two parties exchange a message over a channel they do not control. Each applies a security-related transformation using secret information the opponent lacks, sometimes with a trusted third party to distribute that information beforehand or to arbitrate afterwards. Module 1 designs the transformations and distributes the secrets; Module 2's first half specifies the protocols that use them, which is what PGP, S/MIME, IPsec and TLS are.

munotes.in27

Security Mechanisms, and Which Service Each One Serves

The access model. The threat is not somebody reading the channel but something getting inside: an unwanted person, or unwanted software. Two answers: a gatekeeper function at the boundary, which is the firewall chapters, and internal controls that watch what is already inside, which is the intrusion detection and malicious software chapters. Module 2's second half is nothing but this model.

Every remaining chapter of this book sits in one of those two pictures, and it is worth deciding which one a new topic belongs to before reading it.

What beginners get wrong here

A digital signature does not provide confidentiality. It is the single commonest error in this topic, and X.800's Table 1 settles it: there is no mark in any confidentiality row under the signature column. To get both you sign and then encrypt, which is exactly what PGP does.

Traffic padding is useless unless it is encrypted. If an opponent can tell the filler from the data, the filler hides nothing. X.800 says so.

Notarisation is a mechanism, not an institution. It means registering data with a trusted third party. A certification authority is one instance; a timestamping service is another.

The pervasive mechanisms are examinable. They are usually omitted from notes because they do not map to a service. Trusted functionality, security labels, event detection, security audit trail and security recovery are five easy marks in a question that asks for X.800's mechanisms in full.

Quick revision

  • Mechanism: a process designed to detect, prevent or recover from an attack.
  • Eight specific: encipherment, digital signature, access control, data integrity, authentication exchange, traffic padding, routing control, notarisation.
  • Five pervasive: trusted functionality, security labels, event detection, security audit trail, security recovery.
  • Encipherment is reversible (symmetric or asymmetric) or irreversible (a hash).
  • From Table 1: encipherment serves almost everything; a signature serves no confidentiality service; non-repudiation is the one service encipherment does not serve; traffic flow confidentiality needs encipherment, padding and routing control.
  • Authentication exchange techniques: timestamps and synchronised clocks, two-way and three-way handshakes.
  • Traffic padding works only if the padding is enciphered.
  • Two models: communication (transformation, secret, trusted third party, opponent on the channel) and access (gatekeeper plus internal controls).

Test yourself

1. Define a security mechanism, and give X.800's two categories. A process, or a device incorporating such a process, designed to detect, prevent or recover from a security attack. X.800 divides them into specific security mechanisms, which sit in a protocol layer and provide particular services, and pervasive security mechanisms, which are not specific to any service or layer.

munotes.in28

Security Mechanisms, and Which Service Each One Serves

2. Name the eight specific mechanisms. Encipherment, digital signature, access control, data integrity, authentication exchange, traffic padding, routing control and notarisation.

3. Name the five pervasive mechanisms. Trusted functionality, security labels, event detection, security audit trail and security recovery.

4. Which mechanisms provide non-repudiation, and which one notably does not? A digital signature, a data integrity mechanism and notarisation. Encipherment does not: X.800's Table 1 marks it as not appropriate for either form of non-repudiation, because a shared key lets the verifier forge.

5. Which mechanisms provide traffic flow confidentiality, and why is more than one needed? Encipherment, traffic padding and routing control. Encryption hides the contents but leaves the pattern of traffic visible, so padding is needed to conceal volume and timing and routing control to choose which physical paths carry the data.

6. Does a digital signature provide confidentiality? Justify from the standard. No. In X.800's Table 1 the digital signature column has no entry against any of the four confidentiality services. A signature proves origin and integrity and leaves the data readable; to obtain confidentiality as well you must also encipher.

7. A requirement states that a message must be readable only by the recipient, provably from the sender, unaltered, and undeniable later. Name the services and the smallest set of mechanisms. The services are connectionless confidentiality, data origin authentication, connectionless integrity and non-repudiation with proof of origin. A digital signature over a hash of the message serves the last three; encipherment serves the first. So two mechanisms suffice: sign the hash, then encrypt.

Contents This chapter on its own page

munotes.in29

Chapter Seven

The Symmetric Cipher Model

Syllabus topic Module 1, "Classical Encryption Techniques: Symmetric Cipher Model"

In one line

One key, held by both parties, used to encrypt and to decrypt. That is a symmetric cipher, and until 1976 it was the only kind there was.

In the wording a student can write in an examination: a symmetric cipher, also called conventional, secret-key or single-key encryption, is a scheme with five ingredients: plaintext, the original message; an encryption algorithm, which performs substitutions and transformations on it; a secret key, which is an input to the algorithm and determines what it does; ciphertext, the scrambled output; and a decryption algorithm, which is the encryption algorithm run in reverse and which recovers the plaintext from the ciphertext using the same key. X.800 puts the defining property precisely: in symmetric encipherment, "knowledge of the encipherment key implies knowledge of the decipherment key and vice versa".

Why the model is written down before any cipher

Because it tells you exactly what a cipher has to be, and therefore what a question about a cipher can ask. Every cipher in the next fifteen chapters is an instance of this one picture, so the picture is worth getting exactly right, and the two requirements below are worth more marks than the five ingredients.

The five ingredients, in the order a program uses them

  1. Plaintext. The message or data as it stands. Written P or M.
  2. Secret key. A value independent of the plaintext and of the algorithm. Written K. Its exact bits decide which of the algorithm's many possible transformations is performed.
  3. Encryption algorithm. Written E. It performs substitutions and permutations on the plaintext under the control of the key.
  4. Ciphertext. The output, written C. It depends on both the plaintext and the key: for one plaintext, two different keys give two unrelated ciphertexts.
  5. Decryption algorithm. Written D. The encryption algorithm run backwards, taking the ciphertext and the same key and producing the plaintext.

In symbols, which is how an answer should state it:

C = E(K, P)

P = D(K, C)

P = D(K, E(K, P))

The two requirements, which are what the marks are in

Requirement 1: the algorithm must be strong enough. An opponent who knows the algorithm and who has one or more ciphertexts should be unable to decipher the ciphertext or to work out the key. Notice the phrasing carefully: the opponent knows the algorithm. That is assumed, not conceded. It is the requirement that a student most often states too weakly, writing "the opponent should not be able to break it", which says nothing about what the opponent is given.

Requirement 2: sender and receiver must have obtained the key in a secure fashion, and must keep it secure. The security of a symmetric cipher rests on the secrecy of the key, and on nothing else. If somebody learns the key, all communication using it is readable.

munotes.in30

The Symmetric Cipher Model

Requirement 2 is the harder of the two in practice, and it is the reason a third of this syllabus is about key management rather than about ciphers. Designing a cipher is a problem you solve once, publicly, and then everybody uses the answer. Getting a key to the other party is a problem you have again with every new party, and it is why the Diffie-Hellman chapter exists.

Kerckhoffs's principle: why the algorithm is published

A student's instinct is that a secret algorithm must be safer than a published one, because the opponent knows less. It is the opposite, and the reason is practical rather than mathematical.

A secret algorithm cannot be reviewed. The only way to gain confidence that a cipher is strong is for many skilled people to attack it and fail. A cipher nobody has seen has not been tested; it has only not been tested yet. AES was chosen after a five-year open competition in which everybody was invited to break every candidate. That is evidence. "Nobody has broken our secret cipher" is not evidence, because nobody has tried.

A secret algorithm cannot be replaced. Keys change easily and algorithms do not. If security rests on the algorithm and the algorithm leaks, everything must be rebuilt. If security rests on the key and the key leaks, you change the key. Putting the secret in the smaller, cheaper, more replaceable component is simply good engineering.

A secret algorithm cannot be standardised. Two parties who have never met cannot interoperate over a secret. Every protocol in Module 2 works because both ends can look the algorithm up in a published document.

The principle, in one line: a cryptosystem should be secure even if everything about the system, except the key, is public knowledge. That is Kerckhoffs's principle, from 1883, and it is the working assumption of this entire subject. Every algorithm on this syllabus is published: DES in FIPS 46-3, AES in FIPS 197, the hash functions in FIPS 180-4, RSA in RFC 8017. You can read all of them. They are secure anyway.

The opposite practice has a name, security through obscurity, and it is used as a criticism. It is not worthless as one layer among many, but as the foundation it fails, because obscurity is lost once and cannot be restored.

A worked example: what the key actually decides

The setup. Take the simplest possible symmetric cipher: shift every letter forward by K positions in the alphabet. Plaintext MEET ME AFTER THE TOGA PARTY.

munotes.in31

The Symmetric Cipher Model

Step 1: the algorithm is public. Everybody, including the opponent, knows that the rule is "shift by K". Requirement 1 says the cipher must be secure anyway.

Step 2: the key is secret. Suppose K is 3. The program below performs the encryption and the decryption, and prints both.

ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

def shift(text, k):
    out = ""
    for ch in text.upper():
        if ch in ALPHABET:
            out += ALPHABET[(ALPHABET.index(ch) + k) % 26]
        else:
            out += ch
    return out

plaintext = "MEET ME AFTER THE TOGA PARTY"
key = 3

ciphertext = shift(plaintext, key)
recovered = shift(ciphertext, -key)

print("plaintext :", plaintext)
print("key       :", key)
print("ciphertext:", ciphertext)
print("recovered :", recovered)
print("P = D(K, E(K, P)) is", recovered == plaintext)
plaintext : MEET ME AFTER THE TOGA PARTY
key       : 3
ciphertext: PHHW PH DIWHU WKH WRJD SDUWB
recovered : MEET ME AFTER THE TOGA PARTY
P = D(K, E(K, P)) is True

Step 3: read the model off the run. shift with a positive key is E; shift with a negative key is D; 3 is K. The last line is the identity from section 3, verified rather than asserted. Both parties need the number 3 and nothing else, and anybody who learns the number 3 reads everything.

Step 4: and this cipher fails requirement 1. There are only 25 useful keys, so an opponent who knows the algorithm tries all of them in a fraction of a second. That is not a criticism of the model; the model is fine. It is a statement about this particular algorithm's keyspace, and the next chapters are about making the keyspace large enough that requirement 1 is actually met.

Distinctions that carry marks

SymmetricAsymmetric
Keysone, sharedtwo, related, one public
X.800's testknowing the encipherment key implies knowing the decipherment keyit does not
Who must share a secretboth parties, in advancenobody
Speedfastslow, by a factor of hundreds or more
Keys needed for N partiesN(N - 1)/22N
Gives non-repudiationnoyes
Used forbulk datakeys, and signatures
CryptographyCryptanalysisCryptology
Isthe design of ciphersthe breaking of ciphers without the keyboth together
Practised bythe designerthe attacker, and the reviewerthe field
Block cipherStream cipher
Processesa fixed-size block at a timeone bit or byte at a time
Examples hereDES, 3DES, AESRC4

What beginners get wrong here

"Symmetric" does not mean the two algorithms are identical. It means the two keys are the same, or trivially derived from each other. In DES the decryption algorithm is the encryption algorithm with the subkeys reversed, which is not the same thing as being identical.

munotes.in32

The Symmetric Cipher Model

The key is not a password. A key is a value of a fixed length, usually 128 or 256 bits, chosen to be unpredictable. A password is something a human remembers and is far weaker; turning one into the other is a separate job, done by a key derivation function.

Knowing the algorithm is assumed, not a weakness. An answer that offers "keep the algorithm secret" as a countermeasure has misunderstood requirement 1.

Two parties, one key. The N(N - 1)/2 count in the table above is the reason symmetric cryptography alone does not scale, and it is worth being able to compute: ten parties need forty-five keys, a hundred parties need four thousand nine hundred and fifty.

Quick revision

  • Five ingredients: plaintext, encryption algorithm, secret key, ciphertext, decryption algorithm.
  • C = E(K, P) and P = D(K, C).
  • Two requirements: a strong algorithm, against an opponent who knows the algorithm and holds ciphertext; and a securely obtained and kept key.
  • X.800: symmetric means knowing the encipherment key implies knowing the decipherment key.
  • Kerckhoffs's principle: secure even when everything but the key is public. Because a secret algorithm cannot be reviewed, replaced or standardised.
  • Security through obscurity is the opposite practice and fails as a foundation, because obscurity is lost once.
  • Symmetric needs N(N - 1)/2 keys for N parties; asymmetric needs 2N.
  • Cryptography designs, cryptanalysis breaks, cryptology is both.

Test yourself

1. Name the five ingredients of a symmetric cipher. Plaintext, an encryption algorithm, a secret key, ciphertext and a decryption algorithm.

2. State the two requirements for secure use of conventional encryption. First, a strong encryption algorithm: an opponent who knows the algorithm and has access to one or more ciphertexts should be unable to decipher the ciphertext or to determine the key. Second, the sender and receiver must have obtained copies of the secret key in a secure fashion and must keep the key secure.

3. What does it mean to say a cipher is symmetric, in X.800's terms? That knowledge of the encipherment key implies knowledge of the decipherment key, and the other way round; in practice the two are the same key.

4. State Kerckhoffs's principle and give two reasons for it. A cryptosystem should remain secure even if everything about it except the key is public. First, a published algorithm can be attacked by many skilled people, and surviving that is the only real evidence of strength; a secret one is merely untested. Second, a key can be changed cheaply and an algorithm cannot, so the secret belongs in the replaceable part.

5. Why is requirement 2 harder in practice than requirement 1? Because a cipher is designed once and then used by everybody, whereas a key must be delivered securely to every new correspondent, again and again, over channels that may be no safer than the ones the cipher is protecting. Key distribution is therefore the recurring problem, and it is why public-key methods were invented.

munotes.in33

The Symmetric Cipher Model

6. How many keys do twenty parties need to communicate pairwise under symmetric encryption, and under public-key encryption? Symmetric: 20 times 19 divided by 2, which is 190 keys. Public-key: two per party, so 40 keys, of which only the 20 private ones are secret.

7. A vendor says their cipher is safe because nobody knows how it works. What is wrong with that? It is security through obscurity. The claim is unverifiable, because the cipher has not been reviewed by anybody able to attack it; and it is fragile, because a single disclosure or reverse engineering destroys all security at once, with no key to change. Published algorithms with secret keys fail more gracefully and can be evaluated.

Contents This chapter on its own page

munotes.in34

Chapter Eight

Cryptanalysis and the Attack Models

Syllabus topic Module 1, "Classical Encryption Techniques: Symmetric Cipher Model"; Course Outcome OC 1, "attack models"

In one line

Cryptanalysis is breaking a cipher without being given the key. What counts as breaking it depends entirely on what the attacker is assumed to hold, and that assumption is called the attack model.

In the wording a student can write in an examination: there are two general approaches to attacking a symmetric cipher. Cryptanalysis relies on the nature of the algorithm, together with some knowledge of the general characteristics of the plaintext or some sample plaintext and ciphertext pairs, to deduce a specific plaintext or the key itself. Brute force tries every possible key on a piece of ciphertext until an intelligible translation is obtained; on average half of all possible keys must be tried.

Why the attack model comes before the attack

Because "this cipher is secure" is meaningless without it. A cipher can be perfectly safe against an opponent who has only ciphertext and fall apart against one who can choose the plaintext. Both statements can be true of the same cipher, and an examination question that says "can this cipher be broken" is incomplete until you say what the attacker has.

There is a second, sharper reason. The stronger models are the realistic ones. It is tempting to assume the attacker sees only ciphertext, but in practice attackers very often know part of the plaintext: every message begins with the same header, every letter ends with the same closing, every file format starts with the same magic bytes. Designing against the weak model and being attacked in the strong one is how real systems fail, which is why modern ciphers are required to resist the strongest models in the list.

The five attack models, weakest attacker first

1. Ciphertext only. The attacker has the encryption algorithm and some ciphertext. Nothing else. This is the weakest position and the only one in which a cipher with a large keyspace is safe simply by having a large keyspace.

2. Known plaintext. The attacker has the algorithm, some ciphertext, and one or more plaintext and ciphertext pairs formed with the secret key. This is common in reality. If every internal-marks submission begins DEPARTMENT: then the attacker knows eleven characters of every message's plaintext before starting. The Hill cipher, which is unbreakable under model 1 for a large enough matrix, falls immediately under model 2, and the Hill chapter shows why.

3. Chosen plaintext. The attacker can choose plaintext and obtain the matching ciphertext. This sounds unrealistic and is not: any system that encrypts whatever a user submits, such as a web application that stores your search terms encrypted, hands the attacker this power. It is the model in which the determinism of textbook RSA becomes fatal.

munotes.in35

Cryptanalysis and the Attack Models

4. Chosen ciphertext. The attacker can choose a ciphertext and obtain the matching decrypted plaintext, or at least learn whether decryption succeeded. Systems that report "bad padding" hand this over, and that one bit of feedback is enough to recover plaintext byte by byte. The chapter on cipher block chaining shows the mechanism.

5. Chosen text. Both 3 and 4 at once. The strongest attacker, and the model a modern cipher is expected to resist.

Two kinds of security, and the one that matters

Unconditionally secure. No amount of ciphertext, and no amount of computing, is enough to recover the plaintext, because the ciphertext simply does not contain enough information to determine it. Exactly one cipher in this syllabus is unconditionally secure, the one-time pad, and its chapter proves it.

Computationally secure. Either the cost of breaking the cipher exceeds the value of the information, or the time required to break it exceeds the useful lifetime of the information. Every other cipher in this book is in this class. Notice that both tests are about economics, not mathematics: a cipher protecting a share price for one minute needs far less than one protecting a medical record for fifty years.

This is why key length recommendations carry dates, and why NIST publishes transition schedules rather than permanent answers.

The cost of brute force, computed

The listing below takes the keyspaces of the ciphers in this syllabus and works out how long an exhaustive search takes at two speeds: one thousand million keys a second, which is roughly a single modern processor core, and one million million million keys a second, which is far beyond any machine that exists and is included to show that the large keyspaces do not care.

SECONDS_IN_YEAR = 365.25 * 24 * 60 * 60

def show(name, keyspace_bits, extra=""):
    keys = 2 ** keyspace_bits
    average = keys // 2
    for rate, label in ((10 ** 9, "1e9/s"), (10 ** 18, "1e18/s")):
        years = average / rate / SECONDS_IN_YEAR
        if years < 1 / 365.25:
            t = "%.3g seconds" % (average / rate)
        elif years < 1:
            t = "%.3g days" % (years * 365.25)
        else:
            t = "%.3g years" % years
        print("%-22s %4d bits  %-7s %s" % (name, keyspace_bits, label, t))
    if extra:
        print("%-22s %s" % ("", extra))

show("Caesar", 4.7, "25 keys in all, so the bits are only an analogy")
show("DES", 56)
show("two-key 3DES", 112)
show("AES-128", 128)
show("AES-256", 256)
Caesar                    4 bits  1e9/s   1.2e-08 seconds
Caesar                    4 bits  1e18/s  1.2e-17 seconds
                       25 keys in all, so the bits are only an analogy
DES                      56 bits  1e9/s   1.14 years
DES                      56 bits  1e18/s  0.036 seconds
two-key 3DES            112 bits  1e9/s   8.23e+16 years
two-key 3DES            112 bits  1e18/s  8.23e+07 years
AES-128                 128 bits  1e9/s   5.39e+21 years
AES-128                 128 bits  1e18/s  5.39e+12 years
AES-256                 256 bits  1e9/s   1.83e+60 years
AES-256                 256 bits  1e18/s  1.83e+51 years
munotes.in36

Cryptanalysis and the Attack Models

Read four things out of that.

DES at one core is 1.14 years, which is why DES fell. Not because the number is large but because it is not large enough: a thousand such cores bring it to about ten hours, and at the fanciful rate in the second row it is thirty-six thousandths of a second. In 1998 a purpose-built machine did it in under three days, and the chapter on the strength of DES gives that machine's name and date.

AES-128 at a million million million keys a second is still more than five million million years. Brute force against a 128-bit key is not a matter of waiting for better hardware; the number does not come down within any span that means anything, and a machine running at a million million million keys a second does not exist.

The jump from 56 to 112 bits multiplies the time by about seventy-two thousand million million, from 1.14 years to 8.23e+16 years. Doubling a key length does not double the work, it squares the size of the keyspace, and that is the single most important intuition in this chapter.

The Caesar row is honest about being an analogy, and the printed bit count is not. The program was given 4.7 bits, which is 26 keys, and printed it with an integer format as 4; the timing beside it was computed from the 26. The timing is what matters: it is instantaneous, which is the point of the next chapter. It is also a small demonstration of why a number in a book should be produced by the program that printed it, because a bit count of 4 would be wrong and a reader would have no way to know.

A worked example: what an attacker actually does

The setup. An opponent captures ciphertext from a college portal. They do not know the cipher's key. They know, because the algorithm is published, that it is AES with a 128-bit key.

Step 1: consider brute force. From the table, out of the question.

Step 2: consider cryptanalysis of the algorithm. AES has been attacked publicly since 1998 and no attack materially better than brute force is known against the full cipher. So this route is closed too.

Step 3: attack something else. And this is the real lesson. The opponent stops attacking the cipher and attacks the key, the implementation or the person: a password that generates the key and can be guessed; a key stored in a file that is backed up to a public bucket; a timing difference in the implementation; a clerk who will read out a code over the telephone. Every successful real attack in Module 2 of this syllabus is of this kind.

munotes.in37

Cryptanalysis and the Attack Models

The step that carries the marks. "The cipher is strong" and "the system is secure" are different claims. A question asking you to assess a system's security is not asking about AES's key length; it is asking where the weakest point is, and it is almost never the cipher.

Distinctions that carry marks

ModelThe attacker hasRealistic?
Ciphertext onlyalgorithm, ciphertextyes, the minimum
Known plaintextthe above, plus plaintext and ciphertext pairs under the same keyvery, because of fixed headers
Chosen plaintextthe above, plus plaintext of their choosing encryptedyes, whenever a system encrypts user input
Chosen ciphertextthe above, plus ciphertext of their choosing decrypted, or an error messageyes, whenever a system reports why decryption failed
Chosen textboth of the abovethe model a modern cipher must resist
CryptanalysisBrute force
Usesthe structure of the algorithm, and knowledge of the plaintextnothing but the keyspace
Costvaries, and can be very smallfixed and computable in advance
Defeated bygood designa long key
Tells yousomething is wrong with the ciphernothing about the cipher
Unconditionally secureComputationally secure
Meansthe ciphertext does not determine the plaintext, whatever you dobreaking it costs more than the data is worth, or takes longer than the data matters
Examplethe one-time padevery other cipher here
Depends on a datenoyes

What beginners get wrong here

Brute force is not cryptanalysis. They are the two separate approaches in the definition, and a question that asks for "approaches to attacking a cipher" wants both, named and distinguished.

"On average half the keys" is part of the definition. Exhaustive search succeeds after half the keyspace on average, not after all of it, and the factor of two is worth stating because it is in the standard phrasing.

A large keyspace does not make a cipher strong. It makes brute force infeasible, which is a different claim. The monoalphabetic cipher of the next chapter but one has a keyspace of twenty-six factorial, which is far beyond brute force, and a schoolchild can break it with a pencil.

"Broken" rarely means "plaintext recovered". In cryptanalysis a cipher is considered broken when any attack does better than brute force, even if it is still far too expensive to run. That is why a cipher can be "broken" in a paper and still be safe to use for years, and why the retirement dates in the trend chapter lag the attacks.

munotes.in38

Cryptanalysis and the Attack Models

Quick revision

  • Two approaches: cryptanalysis (uses the algorithm's structure and knowledge of the plaintext) and brute force (tries every key; on average half of them).
  • Five models, weakest attacker first: ciphertext only, known plaintext, chosen plaintext, chosen ciphertext, chosen text.
  • Known plaintext is realistic because of fixed headers and formats. A modern cipher must resist chosen text.
  • Unconditionally secure: only the one-time pad. Computationally secure: everything else, and the test is economic.
  • Computed: DES 56 bits is about 1.14 years at a thousand million keys a second; AES-128 is beyond any machine.
  • Doubling the key length squares the keyspace.
  • A strong cipher does not make a secure system; the weak point is the key, the implementation or the person.
  • "Broken" in cryptanalysis means "better than brute force", not "readable".

Test yourself

1. Name and distinguish the two general approaches to attacking a symmetric cipher. Cryptanalysis uses the nature of the algorithm together with knowledge of the general characteristics of the plaintext, or some plaintext and ciphertext pairs, to deduce a plaintext or the key. Brute force tries every possible key until an intelligible translation appears, and on average succeeds after half the keyspace. Cryptanalysis exploits design; brute force ignores it.

2. List the five attack models and what the attacker holds in each. Ciphertext only: the algorithm and ciphertext. Known plaintext: also one or more plaintext and ciphertext pairs under the same key. Chosen plaintext: also the ability to encrypt plaintext of their choice. Chosen ciphertext: also the ability to decrypt ciphertext of their choice, or to learn whether decryption succeeded. Chosen text: chosen plaintext and chosen ciphertext together.

3. Why is known plaintext a realistic model rather than a pessimistic one? Because real messages have predictable parts: standard headers, fixed salutations, file format signatures, repeated field names. An attacker who knows the message format therefore knows some plaintext before starting, without any special access.

4. Distinguish unconditionally secure from computationally secure, and name a cipher in each class. An unconditionally secure cipher cannot be broken however much ciphertext and computing power are available, because the ciphertext does not determine the plaintext; the one-time pad is the only example. A computationally secure cipher is one where the cost of breaking exceeds the value of the information or the time exceeds its useful life; DES, AES and RSA are all in this class.

5. Roughly how long does exhaustive search of a 56-bit key take at a thousand million keys a second, and what does that tell you about DES? About 1.14 years on average. It tells you DES was defensible in 1977 and indefensible once a thousand such processors, or a purpose-built machine, could be pointed at it, which is what happened in the 1990s. The cipher did not change; the cost of computing did.

munotes.in39

Cryptanalysis and the Attack Models

6. Why does a large keyspace not by itself make a cipher strong? Because cryptanalysis does not search the keyspace. A monoalphabetic substitution has twenty-six factorial keys, which is far beyond brute force, and yet letter frequencies in the ciphertext reveal the mapping directly. Resistance to brute force and resistance to cryptanalysis are separate properties and both are required.

7. What does a cryptanalyst mean by saying a cipher is "broken"? That some attack performs better than exhaustive search, even if it remains far too expensive to carry out. It is a statement about the design rather than about present-day readability, which is why an algorithm can be broken in a paper years before it is formally withdrawn from use.

Contents This chapter on its own page

munotes.in40

Chapter Nine

The Caesar Cipher, and Breaking It in Twenty-five Tries

Syllabus topic Module 1, "Classical Encryption Techniques: Substitution Techniques"

In one line

Shift every letter along the alphabet by a fixed amount. The amount is the key, there are only twenty-five useful values, and that is the whole of both the cipher and its downfall.

In the wording a student can write in an examination: the Caesar cipher is the earliest known substitution cipher, attributed to Julius Caesar, in which each letter of the plaintext is replaced by the letter standing a fixed number of places further down the alphabet, wrapping round from Z to A. With the alphabet numbered A as 0 to Z as 25, encryption and decryption are

C = E(k, p) = (p + k) mod 26

p = D(k, C) = (C - k) mod 26

where k takes a value from 1 to 25.

Why it is in the syllabus at all

Because three ideas arrive with it and stay for the rest of the paper: that encryption can be arithmetic on letters rather than a physical trick; that the key is separate from the algorithm, so one algorithm gives twenty-five different ciphers; and that the size of the keyspace decides whether brute force works. Everything after this chapter makes the keyspace bigger, and the reason is on this page.

It is also the cipher every examiner uses to set a five-mark question that can be answered in five minutes, so the encryption and decryption should be something you can do by hand without hesitating.

Encrypting by hand

Number the alphabet A as 0 through Z as 25. To encrypt with k of 3, add 3 and reduce modulo 26.

MEET ME AFTER THE TOGA PARTY becomes PHHW PH DIWHU WKH WRJD SDUWB.

Take the last letter as the one worth checking, because it is the one that wraps. Y is 24; 24 plus 3 is 27; 27 modulo 26 is 1; and 1 is B. Spaces and punctuation are usually left as they are, which incidentally helps the attacker by preserving the word lengths, and a careful implementation strips them.

To decrypt, subtract. B is 1; 1 minus 3 is minus 2; minus 2 modulo 26 is 24; and 24 is Y. The step students get wrong is that last one: in modular arithmetic a negative result is brought back into the range by adding 26, so minus 2 becomes 24 and not something invalid.

Breaking it, with the program that does it

Three facts make the break trivial, and an answer should state all three.

  1. The encryption and decryption algorithms are known.
  2. There are only 25 keys to try.
  3. The language of the plaintext is known and easily recognisable.

The program below takes a ciphertext, tries every shift, and scores each candidate by how far its letter frequencies are from English's. The score is a chi-squared statistic: for each letter, take how many times it appeared, subtract how many times English would expect it to appear in a text of that length, square the difference, divide by the expectation, and add all twenty-six up. A low score means the candidate looks like English.

munotes.in41

The Caesar Cipher, and Breaking It in Twenty-five Tries

ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

ENGLISH = {"E": 12.70, "T": 9.06, "A": 8.17, "O": 7.51, "I": 6.97, "N": 6.75,
           "S": 6.33, "H": 6.09, "R": 5.99, "D": 4.25, "L": 4.03, "C": 2.78,
           "U": 2.76, "M": 2.41, "W": 2.36, "F": 2.23, "G": 2.02, "Y": 1.97,
           "P": 1.93, "B": 1.29, "V": 0.98, "K": 0.77, "J": 0.15, "X": 0.15,
           "Q": 0.10, "Z": 0.07}

def shift(text, k):
    return "".join(ALPHABET[(ALPHABET.index(c) + k) % 26] if c in ALPHABET else c
                   for c in text.upper())

def fit(text):
    letters = [c for c in text if c in ALPHABET]
    n = len(letters)
    score = 0.0
    for c in ALPHABET:
        seen = letters.count(c)
        expected = ENGLISH[c] * n / 100
        score += (seen - expected) ** 2 / expected
    return score

ciphertext = "WKH TXHVWLRQ SDSHU OHDNHG IURP WKH SULQWHUV RQ WXHVGDB QLJKW"

results = sorted((fit(shift(ciphertext, -k)), k) for k in range(26))
print("the three best fits of all 26 shifts:")
for score, k in results[:3]:
    print("  k = %2d  score %7.1f  %s" % (k, score, shift(ciphertext, -k)))
print()
print("the three worst:")
for score, k in results[-3:]:
    print("  k = %2d  score %7.1f  %s" % (k, score, shift(ciphertext, -k)))
the three best fits of all 26 shifts:
  k =  3  score    29.3  THE QUESTION PAPER LEAKED FROM THE PRINTERS ON TUESDAY NIGHT
  k = 15  score    99.4  HVS EISGHWCB DODSF ZSOYSR TFCA HVS DFWBHSFG CB HISGROM BWUVH
  k = 16  score   189.3  GUR DHRFGVBA CNCRE YRNXRQ SEBZ GUR CEVAGREF BA GHRFQNL AVTUG

the three worst:
  k = 23  score  1455.7  ZNK WAKYZOUT VGVKX RKGQKJ LXUS ZNK VXOTZKXY UT ZAKYJGE TOMNZ
  k = 17  score  1750.7  FTQ CGQEFUAZ BMBQD XQMWQP RDAY FTQ BDUZFQDE AZ FGQEPMK ZUSTF
  k =  8  score  1952.9  OCZ LPZNODJI KVKZM GZVFZY AMJH OCZ KMDIOZMN JI OPZNYVT IDBCO

Read three things out of the run.

The right answer is first, and not narrowly. The correct shift scores 29.3 and the second best scores 99.4, more than three times worse. The gap is what makes the attack automatic: no human needs to read twenty-six candidates, the program can simply report the best.

A human would not have needed the score at all. The first line is English and the others are not, and that is the honest version of the attack: the scoring exists so that a machine can do what a person does at a glance.

munotes.in42

The Caesar Cipher, and Breaking It in Twenty-five Tries

Nothing about the cipher was used. The attack did not analyse the Caesar cipher's structure. It tried every key. That is brute force, and it is available here only because there are twenty-six keys and not because the cipher has any particular flaw.

A worked example in both directions

Encrypt ATTACK AT DAWN with a key of 11.

A is 0; 0 plus 11 is 11; 11 is L. T is 19; 19 plus 11 is 30; 30 modulo 26 is 4; 4 is E. C is 2, giving 13, which is N. K is 10, giving 21, which is V. D is 3, giving 14, which is O. W is 22; 22 plus 11 is 33; 33 modulo 26 is 7; 7 is H. N is 13, giving 24, which is Y.

So ATTACK AT DAWN becomes LEELNV LE OLHY.

Decrypt LEEL with the same key. L is 11; 11 minus 11 is 0, which is A. E is 4; 4 minus 11 is minus 7; minus 7 plus 26 is 19, which is T. So LEEL is ATTA, the opening of the plaintext, and the check works.

Note the two things that give the game away even without trying keys. The doubled EE in LEEL sits where TT sits in the plaintext, and the word lengths are preserved. A substitution cipher that keeps the spaces has handed the attacker the word structure of the message, which is a large part of the work.

Distinctions that carry marks

CaesarMonoalphabetic (general)
Keyone number, 1 to 25any permutation of the alphabet
Keyspace2526 factorial, which is about 4 followed by 26 digits
Broken bybrute force, 25 triesfrequency analysis
Structure preservedletter frequencies, shifted as a blockletter frequencies, individually relabelled
Additive (Caesar)MultiplicativeAffine
Rule(p + k) mod 26(p * a) mod 26(a * p + k) mod 26
Keyka, and it must be coprime to 26the pair (a, k)
Keyspace251212 times 26, which is 312

The affine cipher is worth knowing because it shows what enlarging a keyspace by tinkering achieves: 312 keys instead of 25 is thirteen times better and still nothing. Real improvement comes from changing the shape of the cipher, not from adding a parameter.

What beginners get wrong here

The key is not 26. A shift of 26 is a shift of 0 and leaves the message unchanged, so the useful keys are 1 to 25. Writing "26 possible keys" is the commonest slip in this topic.

Negative results must be brought back positive. Minus 2 modulo 26 is 24, not minus 2. Decryption is where this bites.

munotes.in43

The Caesar Cipher, and Breaking It in Twenty-five Tries

A big keyspace would not save this cipher. The affine cipher has 312 keys and is broken the same afternoon. Caesar's real problem, shared by every monoalphabetic cipher, is that it leaves the letter frequencies of the language intact, and the next chapter is about that.

Keeping the spaces is a real weakness. It is convenient for the example and helpful to the attacker, so a serious implementation removes spaces and punctuation and groups the output in fives.

Quick revision

  • Caesar cipher: C = (p + k) mod 26, p = (C - k) mod 26, with k from 1 to 25.
  • Keyspace 25, so brute force is instantaneous.
  • Three facts that break it: the algorithm is known, there are only 25 keys, the plaintext language is recognisable.
  • The program ranks candidates by a chi-squared fit to English letter frequencies; low is better. The right key scored 29.3 against 99.4 for the runner-up.
  • Brute force uses nothing about the cipher's structure.
  • Variants: multiplicative (p a) mod 26 with a coprime to 26, keyspace 12; affine (a p + k) mod 26, keyspace 312.
  • A negative result in modular arithmetic is brought into range by adding 26.
  • Preserving spaces preserves word lengths and helps the attacker.

Test yourself

1. Give the encryption and decryption rules of the Caesar cipher and the size of its keyspace. With the alphabet numbered A as 0 to Z as 25, encryption is C = (p + k) mod 26 and decryption is p = (C - k) mod 26. There are 25 useful keys, because a key of 0 or 26 leaves the message unchanged.

2. Encrypt CYBER with a key of 5. C is 2 giving 7, which is H. Y is 24; 24 plus 5 is 29; 29 modulo 26 is 3, which is D. B is 1 giving 6, which is G. E is 4 giving 9, which is J. R is 17 giving 22, which is W. The answer is HDGJW.

3. Decrypt WKLV with a key of 3. W is 22 giving 19, which is T. K is 10 giving 7, which is H. L is 11 giving 8, which is I. V is 21 giving 18, which is S. The plaintext is THIS.

4. State the three characteristics that make the Caesar cipher easy to break. The encryption and decryption algorithms are known; there are only 25 keys to try; and the language of the plaintext is known and easily recognised, so a correct decryption announces itself.

5. What is the affine cipher, how large is its keyspace, and why does that not help? It replaces p by (a * p + k) mod 26, where a must be coprime to 26 so that the mapping is reversible, giving 12 choices of a and 26 of k, so 312 keys. It does not help because 312 is still trivially small for brute force, and because the cipher remains monoalphabetic, so frequency analysis breaks it regardless of keyspace.

munotes.in44

The Caesar Cipher, and Breaking It in Twenty-five Tries

6. Why must the multiplier in a multiplicative cipher be coprime to 26? Because only then does multiplication by it permute the residues modulo 26, so that each plaintext letter maps to a distinct ciphertext letter and the mapping can be inverted. If the multiplier shares a factor with 26, several plaintext letters collide on one ciphertext letter and decryption is impossible.

7. Explain how the program in this chapter chooses among the 26 candidate decryptions. For each candidate it computes a chi-squared statistic: for every letter of the alphabet it takes the observed count, subtracts the count English would predict for a text of that length, squares the difference, divides by the predicted count and sums over all 26 letters. The candidate with the lowest total is the one whose letter distribution is closest to English, and in the run shown it was the correct key by a margin of more than three times.

Contents This chapter on its own page

munotes.in45

Chapter Ten

Monoalphabetic Substitution, and Breaking It by Frequency

Syllabus topic Module 1, "Classical Encryption Techniques: Substitution Techniques"

In one line

Replace each letter by any other letter, consistently. The key is the whole scrambled alphabet, which gives an enormous number of keys, and the cipher falls anyway because each letter of English keeps its own frequency under a new name.

In the wording a student can write in an examination: a monoalphabetic substitution cipher replaces each plaintext letter with a ciphertext letter according to a single fixed substitution, used throughout the message. The key is the permutation of the 26 letters, so there are 26 factorial possible keys. Because one plaintext letter always maps to the same ciphertext letter, the statistical structure of the plaintext language survives in the ciphertext, and the cipher is broken by frequency analysis.

Why the enormous keyspace buys nothing

The Caesar cipher had 25 keys, which brute force disposes of. The obvious repair is to allow any rearrangement of the alphabet rather than only a rotation. The keyspace then becomes the number of ways of arranging 26 letters.

import math

keys = math.factorial(26)
print("26 factorial =", keys)
print("that is", len(str(keys)), "digits")

per_second = 10 ** 18
seconds = keys / 2 / per_second
years = seconds / (365.25 * 24 * 60 * 60)
print("at 1e18 keys a second, half the keyspace takes %.3g years" % years)
print("compare the age of the universe, about 1.4e10 years")
26 factorial = 403291461126605635584000000
that is 27 digits
at 1e18 keys a second, half the keyspace takes 6.39 years
compare the age of the universe, about 1.4e10 years

Read that run carefully, because it does not say what a reader expects. At a rate no machine can reach, half of 26 factorial takes six and a half years, not an eternity. So the honest statement is not "26 factorial is beyond brute force for ever"; it is that 26 factorial is about 88 bits, which is beyond a realistic attacker and well short of a modern key. And it does not matter in the slightest, because nobody attacks this cipher by brute force. They attack it with a pencil.

The cipher itself

Any permutation will do as a key, but a permutation of 26 letters is hard to remember, so the classical method builds one from a keyword: write the keyword down, dropping letters that have already appeared, then fill in the rest of the alphabet in order.

The program below builds the cipher alphabet from the keyword EXAMINATION, encrypts a sentence, and then counts the letters of the result.

ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

def keyword_alphabet(keyword):
    seen, out = set(), []
    for c in (keyword + ALPHABET).upper():
        if c in ALPHABET and c not in seen:
            seen.add(c)
            out.append(c)
    return "".join(out)

def encrypt(plain, cipher_alphabet):
    table = {ALPHABET[i]: cipher_alphabet[i] for i in range(26)}
    return "".join(table.get(c, c) for c in plain.upper())

cipher_alphabet = keyword_alphabet("EXAMINATION")
print("plain  alphabet:", ALPHABET)
print("cipher alphabet:", cipher_alphabet)
print()

plain = "THE SEAT NUMBERS FOR THE THEORY EXAMINATION WILL BE DISPLAYED ON THE NOTICE BOARD"
cipher = encrypt(plain, cipher_alphabet)
print("plaintext :", plain)
print("ciphertext:", cipher)
print()

letters = [c for c in cipher if c in ALPHABET]
counts = {c: letters.count(c) for c in ALPHABET}
order = sorted(ALPHABET, key=lambda c: (-counts[c], c))
print("the eight commonest letters of the ciphertext, with counts:")
print("  " + "  ".join("%s:%d" % (c, counts[c]) for c in order[:8]))
print("English's own order begins: E T A O I N S H")
print()
print("total letters:", len(letters))
munotes.in46

Monoalphabetic Substitution, and Breaking It by Frequency

plain  alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ
cipher alphabet: EXAMINTOBCDFGHJKLPQRSUVWYZ

plaintext : THE SEAT NUMBERS FOR THE THEORY EXAMINATION WILL BE DISPLAYED ON THE NOTICE BOARD
ciphertext: ROI QIER HSGXIPQ NJP ROI ROIJPY IWEGBHERBJH VBFF XI MBQKFEYIM JH ROI HJRBAI XJEPM

the eight commonest letters of the ciphertext, with counts:
  I:10  R:7  J:6  B:5  E:5  H:5  O:4  P:4
English's own order begins: E T A O I N S H

total letters: 68

Note in passing how the keyword was reduced: EXAMINATION has repeated letters, and after dropping the repeats it contributes EXAMINTO, after which the alphabet fills in from B.

The break, step by step

Step 1: count the letters. The run above did it. I appears 10 times in 68 letters, R 7 times, J 6 times.

Step 2: match the top of the list to the top of English's list. English runs E, T, A, O, I, N, S, H. The ciphertext runs I, R, J, B and E tied, H, then O and P tied. So the first guesses are I stands for E and R stands for T, and both are correct: the cipher alphabet in the run maps E to I and T to R. Two letters of the key recovered from counting alone.

Step 3: use the structure, not only the counts. This is where the attack becomes fast, and where a machine is worse than a person.

  • ROI appears three times and is the commonest three-letter group. With I as E and R as T, ROI is T?E, and the only sensible English word is THE. So O stands for H. Three letters recovered.
  • XI is a two-letter word ending in E, so it is BE, ME, WE or HE. O is already H, so X stands for B, M or W.
  • JH is a two-letter word whose second letter is H. Of the English two-letter words ending in a letter we have not placed, ON and IN are the candidates, so H stands for N and J for O or I. J was the third commonest letter, and English's fourth is O, so J stands for O. Five letters recovered.
  • VBFF is a four-letter word with a doubled ending: WILL, WELL, FALL, TELL. I is taken by E's image, so the second letter is not E, and WILL fits. Eight letters recovered.
munotes.in47

Monoalphabetic Substitution, and Breaking It by Frequency

Step 4: read the rest off. With E, T, H, N, O, W, I and L in place, the ciphertext reads T?E SE?T N?M?E?S ?O? T?E T?EO?? E???IN?TION WILL ?E ?IS?L??E? ON T?E NOTI?E ?O???, and THE SEAT NUMBERS FOR THE THEORY EXAMINATION WILL BE DISPLAYED ON THE NOTICE BOARD is forced. The whole key follows.

The step that carries the marks. Steps 2 and 3 together. Frequency counts give you the start; digrams, trigrams, doubled letters and word lengths give you the rest. An answer that mentions only single-letter frequencies is describing a quarter of the attack.

What the attacker uses besides single letters

FeatureThe commonest in EnglishWhy it helps
Single lettersE, T, A, O, I, N, S, Hseeds two or three guesses
DigramsTH, HE, IN, ER, AN, RE, EDconfirms pairs, and TH plus HE gives THE
TrigramsTHE, AND, ING, ENT, IONthe single most productive clue
Doubled lettersLL, EE, SS, OO, TT, FFa doubled ciphertext pair is one of a short list
Word lengthsone-letter words are A or Ispaces are a gift to the attacker
Word endingsING, ED, LY, TIONattacks several letters at once

That table is the answer to "how is a monoalphabetic cipher broken", and it is worth more than a recital of the frequency percentages.

Distinctions that carry marks

CaesarKeyword monoalphabeticRandom monoalphabetic
Keya number, 1 to 25a wordthe whole permutation
Keyspace25roughly the number of memorable words26 factorial
Memorabletriviallyyes, which is the pointno
Broken bybrute forcefrequency analysisfrequency analysis
MonoalphabeticPolyalphabetic
A plaintext letter maps toalways the same ciphertext letterdifferent letters at different positions
Letter frequencies in the ciphertextthose of the plaintext language, relabelledflattened
Broken byfrequency analysis directlyfirst find the key length, then frequency analysis per column
Resists brute forceResists cryptanalysis
Caesarnono
Monoalphabeticyesno
One-time padyesyes
AES-128yesas far as anybody knows

The third table is the whole lesson. The monoalphabetic row is the one to remember, because it is the counter-example to "a long key means a strong cipher".

What beginners get wrong here

A large keyspace is not strength. Say it in the answer. This cipher has about 88 bits of key and is broken in an afternoon by hand.

munotes.in48

Monoalphabetic Substitution, and Breaking It by Frequency

Frequency analysis needs enough ciphertext. On a message of ten letters the counts mean nothing, and this is a real limitation, not a technicality: the attack needs a few hundred characters to be comfortable. A very short message under a monoalphabetic cipher may genuinely resist it.

Removing the spaces makes the attack harder, not impossible. It takes away the word lengths and the one-letter words, which is a real loss to the attacker. The digram and trigram counts survive, so the cipher still falls; it simply takes longer.

It is "monoalphabetic" because there is one cipher alphabet, not because there is one key. The word is about the number of substitution alphabets in use, which is what the polyalphabetic ciphers change.

Quick revision

  • Monoalphabetic substitution: one fixed permutation of the alphabet, used throughout. Keyspace 26 factorial, which is 403,291,461,126,605,635,584,000,000, about 88 bits.
  • Brute force does not break it. Frequency analysis does, because each letter keeps the plaintext language's frequency under a new name.
  • The attack: count single letters, match to E T A O I N S H, then use digrams (TH, HE, IN, ER), trigrams (THE, AND, ING), doubled letters and word lengths.
  • In the run: I was E at 10 of 68 letters and R was T at 7, both correct from counting alone; ROI then gave THE.
  • A keyword builds a memorable permutation, at the cost of a much smaller effective keyspace.
  • The lesson: resisting brute force and resisting cryptanalysis are different properties.
  • Frequency analysis needs a few hundred characters; a very short message may resist it.

Test yourself

1. Define a monoalphabetic substitution cipher and state the size of its keyspace. It replaces each plaintext letter with a ciphertext letter according to a single fixed permutation of the alphabet, used for the whole message. The key is that permutation, so there are 26 factorial keys, which is 403,291,461,126,605,635,584,000,000.

2. Why is such a large keyspace no defence? Because the attack is not exhaustive search. A fixed substitution preserves the statistical structure of the plaintext: each letter of English appears in the ciphertext with the frequency it had in the plaintext, merely relabelled. Counting letters, digrams and trigrams therefore recovers the key directly, whatever the keyspace.

3. Describe the attack on a monoalphabetic cipher. Count the ciphertext letters and match the commonest to English's E, T, A, O, I, N, S, H to seed two or three guesses. Then use structure: the commonest trigram is almost always THE, the commonest digrams are TH, HE, IN and ER, a doubled ciphertext pair is one of LL, EE, SS, OO or TT, one-letter words are A or I, and common endings are ING, ED and TION. Fill letters in, read the partial plaintext, and the rest is forced.

munotes.in49

Monoalphabetic Substitution, and Breaking It by Frequency

4. Build the cipher alphabet from the keyword SECURITY. Write the keyword's distinct letters in order, S E C U R I T Y, then continue with the unused letters of the alphabet in order: A, B, D, F, G, H, J, K, L, M, N, O, P, Q, V, W, X, Z. The cipher alphabet is SECURITYABDFGHJKLMNOPQVWXZ.

5. Why does a keyword make the cipher weaker, even though the algorithm is unchanged? Because it reduces the effective keyspace from all 26 factorial permutations to the far smaller number that a memorable word can produce. An attacker can try likely keywords directly, which is a dictionary attack on the key rather than a search of the permutations.

6. What happens to the attack if the spaces are removed? The word lengths, one-letter words and common word endings are no longer visible, which removes several of the attacker's best clues and makes the work longer. Single-letter, digram and trigram frequencies are unaffected, so the cipher still falls; removing spaces raises the cost without changing the outcome.

7. Contrast monoalphabetic and polyalphabetic substitution in terms of what an attacker sees. Under a monoalphabetic cipher each plaintext letter always becomes the same ciphertext letter, so the ciphertext has the plaintext language's frequency profile with the labels changed, and frequency analysis applies immediately. Under a polyalphabetic cipher a plaintext letter becomes different ciphertext letters at different positions, which flattens the frequency profile; the attacker must first determine the key length and then apply frequency analysis to each column separately.

Contents This chapter on its own page

munotes.in50

Chapter Eleven

The Playfair Cipher

Syllabus topic Module 1, "Classical Encryption Techniques: Substitution Techniques"

In one line

Encrypt two letters at a time, using a five by five square of letters built from a keyword. Encrypting pairs rather than single letters flattens the letter frequencies, which is why Playfair survived long after the monoalphabetic ciphers were useless.

In the wording a student can write in an examination: the Playfair cipher is a multiple-letter, or digraph, substitution cipher invented by Charles Wheatstone in 1854 and promoted by Lord Playfair. It treats digrams in the plaintext as single units and translates them into ciphertext digrams, using a 5 by 5 matrix of letters constructed from a keyword, with I and J counted as one letter.

Why encrypting pairs is a real improvement

A monoalphabetic cipher has 26 units to disguise, and English gives each of them a distinctive frequency. Playfair has 26 times 26, that is 676 digrams to disguise, and the frequencies of digrams are much flatter and much harder to count reliably. Relative frequencies of individual letters in Playfair ciphertext show far less variation than the plaintext alphabet's, so the direct attack of the last chapter does not work.

That is why Playfair was used as the British Army's field cipher in the First World War and by the United States Army and other Allied forces in the Second. It is also why it is still in the syllabus: it is the first cipher on this course that is not broken by simply counting letters.

It is broken all the same, and the reason is arithmetic. A few hundred letters of ciphertext give enough digram structure to work with, and the cipher leaves large clues: a plaintext digram and its reverse encrypt to a ciphertext digram and its reverse, and the letters of a pair are never both unchanged.

Building the square

Step 1. Take the keyword. Write its letters into a 5 by 5 grid, left to right and top to bottom, skipping any letter that has already been written.

Step 2. Fill the remaining cells with the rest of the alphabet in order, again skipping letters already present.

Step 3. Because there are 26 letters and only 25 cells, I and J share a cell. The convention taken here, and the one in MU's reading list, is that J is treated as I throughout.

For the keyword MONARCHY the square is built in the program below. Note what the keyword contributes: every letter of MONARCHY is distinct, so all eight go in, and the rest of the alphabet follows from B.

Encrypting: the three rules, and the two preparations

Preparation 1: split the plaintext into pairs. If both letters of a pair are the same, insert a filler letter, conventionally X, between them and start again. So BALLOON becomes BA LX LO ON, because the two Ls would otherwise form a pair.

munotes.in51

The Playfair Cipher

Preparation 2: if the plaintext has an odd number of letters, add a filler at the end.

Now the three rules. For a pair of plaintext letters:

Rule 1, same row. Replace each by the letter to its right, wrapping round from the last column to the first.

Rule 2, same column. Replace each by the letter below it, wrapping round from the bottom row to the top.

Rule 3, neither. Each letter is replaced by the letter in its own row and in the other letter's column. In other words, the two letters mark two corners of a rectangle and you take the other two corners, keeping each letter on its own row.

Rule 3 is the one that goes wrong, and it goes wrong in one specific way: students take the corners in the wrong order. The first ciphertext letter is on the first plaintext letter's row. Keeping to that removes the error entirely.

The program, which shows which rule it used

ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

def square(key):
    seen, out = set(), []
    for c in (key + ALPHABET).upper().replace("J", "I"):
        if c in ALPHABET and c not in seen:
            seen.add(c)
            out.append(c)
    return [out[r * 5:r * 5 + 5] for r in range(5)]

def digraphs(plain):
    t = "".join(c for c in plain.upper() if c in ALPHABET).replace("J", "I")
    pairs, i = [], 0
    while i < len(t):
        a = t[i]
        b = t[i + 1] if i + 1 < len(t) else "X"
        if a == b:
            pairs.append(a + "X")
            i += 1
        else:
            pairs.append(a + b)
            i += 2
    return pairs

def find(sq, c):
    for r in range(5):
        for k in range(5):
            if sq[r][k] == c:
                return r, k

def crypt(sq, pairs, step):
    out = []
    for a, b in pairs:
        ra, ca = find(sq, a)
        rb, cb = find(sq, b)
        if ra == rb:
            out.append(sq[ra][(ca + step) % 5] + sq[rb][(cb + step) % 5])
        elif ca == cb:
            out.append(sq[(ra + step) % 5][ca] + sq[(rb + step) % 5][cb])
        else:
            out.append(sq[ra][cb] + sq[rb][ca])
    return out

sq = square("MONARCHY")
print("the square for the keyword MONARCHY:")
for row in sq:
    print("   " + " ".join(row))
print()

plain = "BALLOON"
pairs = digraphs(plain)
print("plaintext  :", plain)
print("digraphs   :", " ".join(pairs))
enc = crypt(sq, pairs, +1)
print("ciphertext :", "".join(enc))
print()
print("rule used for each pair:")
for p, c in zip(pairs, enc):
    ra, ca = find(sq, p[0]); rb, cb = find(sq, p[1])
    if ra == rb:
        rule = "same row, move right"
    elif ca == cb:
        rule = "same column, move down"
    else:
        rule = "rectangle, swap columns"
    print("   %s -> %s   %s" % (p, c, rule))
print()
back = crypt(sq, [(c[0], c[1]) for c in enc], -1)
print("decrypted  :", "".join(back))
munotes.in52

The Playfair Cipher

the square for the keyword MONARCHY:
   M O N A R
   C H Y B D
   E F G I K
   L P Q S T
   U V W X Z

plaintext  : BALLOON
digraphs   : BA LX LO ON
ciphertext : IBSUPMNA

rule used for each pair:
   BA -> IB   same column, move down
   LX -> SU   rectangle, swap columns
   LO -> PM   rectangle, swap columns
   ON -> NA   same row, move right

decrypted  : BALXLOON

Read four things out of the run.

BA is a same-column pair. B is in row 1, column 3; A is in row 0, column 3. Same column, so each moves down: B becomes I and A becomes B. This is the pair students misread, because B and A look like they should form a rectangle.

ON is a same-row pair. Both are in row 0, so each moves right: O becomes N and N becomes A. Notice that the ciphertext NA contains a letter from the plaintext, which is normal and not an error.

Two pairs used rule 3, and in both the first ciphertext letter came from the first plaintext letter's row: for LO, L is row 3 and O is column 1, giving row 3 column 1, which is P.

The decryption returns BALXLOON, not BALLOON. The filler X is still there. The receiver has to notice that LXL is not a word and remove the X. That is a genuine weakness of the scheme and a genuine examination trap: when asked to decrypt, remove the fillers and say that you did.

Decrypting

Decryption uses the same three rules with the direction reversed: same row, move left; same column, move up; rectangle, unchanged, because taking the other two corners twice returns you to where you started. That last point is worth stating in an answer, because it explains why rule 3 needs no reversal.

A second worked example, by hand

Encrypt HIDE THE GOLD with the same square.

Pairs: HI DE TH EG OL DX. The last pair needed a filler because the letter count is odd.

  • HI: H is row 1 column 1, I is row 2 column 3. Rectangle. H takes row 1 column 3, which is B; I takes row 2 column 1, which is F. So BF.
  • DE: D is row 1 column 4, E is row 2 column 0. Rectangle. D takes row 1 column 0, which is C; E takes row 2 column 4, which is K. So CK.
  • TH: T is row 3 column 4, H is row 1 column 1. Rectangle. T takes row 3 column 1, which is P; H takes row 1 column 4, which is D. So PD.
  • EG: E is row 2 column 0, G is row 2 column 2. Same row, move right: F and I. So FI.
  • OL: O is row 0 column 1, L is row 3 column 0. Rectangle. O takes row 0 column 0, which is M; L takes row 3 column 1, which is P. So MP.
  • DX: D is row 1 column 4, X is row 4 column 3. Rectangle. D takes row 1 column 3, which is B; X takes row 4 column 4, which is Z. So BZ.
munotes.in53

The Playfair Cipher

Ciphertext: BFCKPDFIMPBZ.

What an attacker gets from the structure

Playfair leaks in ways worth knowing, because a question about its weaknesses expects more than "it is old".

A digram and its reverse give a ciphertext digram and its reverse. If AB encrypts to RS, then BA encrypts to SR. So the pairs AB and BA are visibly related in the ciphertext, and English has many such pairs (ER and RE, ON and NO, ES and SE).

No letter ever maps to itself. Every rule moves both letters, so a ciphertext letter is never in the same place as its plaintext letter. That halves the candidate space at every step.

Doubled letters cannot appear in the ciphertext. The preparation eliminated them from the plaintext, so LL never appears in the output. A ciphertext with no doubled letters at all is a signature of Playfair.

676 digrams, and about 600 keys' worth of square. Digram frequency analysis on a few hundred letters recovers enough of the square to guess the keyword, and the keyword then gives the rest.

Distinctions that carry marks

MonoalphabeticPlayfair
Unit encryptedone lettertwo letters
Units to disguise26676
Keya permutation of 26 lettersa 5 by 5 square, from a keyword
Frequencies in the ciphertextthe plaintext language's, relabelledmuch flatter
Broken bysingle-letter frequency analysisdigram frequency analysis, a few hundred letters
Used in warnoyes, by the British Army in the First World War
RuleConditionEncryptionDecryption
1same rowmove right, wrappingmove left, wrapping
2same columnmove down, wrappingmove up, wrapping
3neitherother two corners, each on its own rowthe same operation

What beginners get wrong here

The order in rule 3. The first ciphertext letter stays on the first plaintext letter's row. Swapping them is the commonest error in this topic.

Forgetting to split a doubled pair. BALLOON is BA LX LO ON, not BA LL OO N. Missing the filler shifts every subsequent pair and destroys the answer.

munotes.in54

The Playfair Cipher

Forgetting that I and J share a cell. A plaintext J becomes I before encryption, and on decryption an I may be either. The receiver decides from context.

Thinking Playfair is unbreakable because the frequencies look flat. They are flatter, not flat. A few hundred letters is enough.

Leaving the fillers in on decryption. The recovered text is BALXLOON, and the answer is BALLOON. Say that you removed it.

Quick revision

  • Playfair: digram substitution using a 5 by 5 square from a keyword, I and J sharing a cell. Wheatstone, 1854.
  • 676 digrams to disguise instead of 26 letters, so the letter frequencies are much flatter.
  • Preparation: split a doubled pair with an X, and pad an odd length.
  • Three rules: same row move right; same column move down; otherwise the other two corners, first ciphertext letter on the first plaintext letter's row.
  • Decryption: left, up, and rule 3 unchanged.
  • BALLOON under MONARCHY is IBSUPMNA, and decrypting gives BALXLOON: strip the filler.
  • Leaks: a reversed digram gives a reversed ciphertext digram; no letter maps to itself; no doubled letters in the ciphertext.
  • Broken by digram frequency analysis on a few hundred letters.

Test yourself

1. Construct the Playfair square for the keyword SECURITY. Row by row: S E C U R; I T Y A B; D F G H K; L M N O P; Q V W X Z. The keyword's distinct letters come first, then the remaining letters in order, with I and J sharing the I cell.

2. Encrypt MEET with the MONARCHY square. Pairs are ME and ET. M is row 0 column 0 and E is row 2 column 0, so they share a column: M becomes C and E becomes L, giving CL. E is row 2 column 0 and T is row 3 column 4, a rectangle: E takes row 2 column 4, which is K, and T takes row 3 column 0, which is L, giving KL. The ciphertext is CLKL.

3. Why is Playfair harder to break than a monoalphabetic cipher? Because it encrypts two letters at a time, so the attacker must work with 676 digrams rather than 26 letters, and digram frequencies are much flatter and need far more ciphertext to distinguish reliably. Single-letter frequency analysis, which breaks a monoalphabetic cipher at once, does not work.

4. State the three encryption rules and how each is reversed. Same row: move each letter one place right, wrapping; reversed by moving left. Same column: move each letter one place down, wrapping; reversed by moving up. Neither: take the other two corners of the rectangle, each ciphertext letter on its own plaintext letter's row; reversed by the same operation, because applying it twice returns the original.

munotes.in55

The Playfair Cipher

5. What is done with a doubled letter in the plaintext, and what must the receiver do? A filler letter, conventionally X, is inserted between the two, so the pair is split. On decryption the receiver recovers the filler as part of the text and must remove it by inspection, since BALXLOON is plainly BALLOON.

6. Name three structural weaknesses of Playfair that help an attacker. A plaintext digram and its reverse encrypt to a ciphertext digram and its reverse, so related pairs are visible. No letter is ever encrypted to itself, which halves the possibilities at each step. Doubled letters never occur in the ciphertext, which both identifies the cipher and constrains the plaintext.

7. Why does rule 3 need no separate decryption rule? Because it maps the two letters to the other two corners of the same rectangle, each staying on its own row. Applying that operation to the ciphertext pair returns the original corners, so encryption and decryption are the same step.

Contents This chapter on its own page

munotes.in56

Chapter Twelve

The Hill Cipher

Syllabus topic Module 1, "Classical Encryption Techniques: Substitution Techniques"

In one line

Treat a block of letters as a vector of numbers and multiply it by a matrix, modulo 26. The matrix is the key, and it must be invertible modulo 26 or the message can never be decrypted.

In the wording a student can write in an examination: the Hill cipher, developed by the mathematician Lester Hill in 1929, is a polygraphic substitution cipher that takes m successive plaintext letters and substitutes for them m ciphertext letters, using m linear equations in which each character is assigned a numerical value from A as 0 to Z as 25. In matrix form, with P a column vector of m plaintext values, K an m by m key matrix and C the ciphertext vector,

C = K P mod 26

P = K inverse C mod 26

and K must have an inverse modulo 26.

Why it is on the syllabus

Because it is the first cipher here that is strong against the attack you have been taught, and it is the first that is destroyed by an attack you have not. That pairing is the lesson.

A 3 by 3 Hill cipher completely hides single-letter frequency information: each ciphertext letter depends on three plaintext letters, so the ciphertext's letter counts carry nothing usable. A larger matrix hides digram and trigram information too. Against a ciphertext-only attacker, Hill with a reasonably large matrix is genuinely hard.

Against a known-plaintext attacker it is trivial, because the cipher is linear and linear systems can be solved. Section 6 solves one. The moral is the one from the chapter on attack models: a claim of strength that does not name the attacker's position is not a claim.

Encrypting

Step 1: number the letters. A is 0 through Z is 25.

Step 2: cut the plaintext into blocks of m, padding the last block if necessary. The pad here is X.

Step 3: for each block, multiply. For a 3 by 3 key, the first ciphertext letter is the first row of K dotted with the plaintext block, reduced modulo 26, and likewise for the second and third rows.

The program below does it, and also computes the inverse key and decrypts.

ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

def egcd(a, b):
    if b == 0:
        return a, 1, 0
    g, x, y = egcd(b, a % b)
    return g, y, x - (a // b) * y

def modinv(a, m):
    g, x, _ = egcd(a % m, m)
    return None if g != 1 else x % m

def det(m):
    n = len(m)
    if n == 1:
        return m[0][0] % 26
    if n == 2:
        return (m[0][0] * m[1][1] - m[0][1] * m[1][0]) % 26
    total = 0
    for j in range(n):
        minor = [[m[r][c] for c in range(n) if c != j] for r in range(1, n)]
        total += ((-1) ** j) * m[0][j] * det(minor)
    return total % 26

def inverse(k):
    n = len(k)
    d = det(k)
    di = modinv(d, 26)
    if di is None:
        return None
    adj = [[0] * n for _ in range(n)]
    for i in range(n):
        for j in range(n):
            minor = [[k[r][c] for c in range(n) if c != i] for r in range(n) if r != j]
            adj[i][j] = (((-1) ** (i + j)) * det(minor)) % 26
    return [[(di * adj[i][j]) % 26 for j in range(n)] for i in range(n)]

def apply(text, k):
    n = len(k)
    t = "".join(c for c in text.upper() if c in ALPHABET)
    t += "X" * (-len(t) % n)
    out = []
    for i in range(0, len(t), n):
        v = [ALPHABET.index(c) for c in t[i:i + n]]
        for row in k:
            out.append(ALPHABET[sum(row[j] * v[j] for j in range(n)) % 26])
    return "".join(out)

def show(name, m):
    print(name)
    for row in m:
        print("   " + " ".join("%2d" % v for v in row))

K = [[17, 17, 5], [21, 18, 21], [2, 2, 19]]
show("key matrix K:", K)
print("det K mod 26 =", det(K), "and its inverse mod 26 =", modinv(det(K), 26))
show("K inverse mod 26:", inverse(K))
plain = "PAYMOREMONEY"
cipher = apply(plain, K)
print("plaintext :", plain)
print("ciphertext:", cipher)
print("decrypted :", apply(cipher, inverse(K)))
print()

BAD = [[6, 24], [1, 13]]
show("a key that cannot be used:", BAD)
print("det =", det(BAD), " gcd(det, 26) =", egcd(det(BAD), 26)[0],
      " inverse =", modinv(det(BAD), 26))
print("so this key encrypts and can never decrypt. Check it:")
print("   AB encrypts to", apply("AB", BAD), "and so does", end=" ")
for a in ALPHABET:
    for b in ALPHABET:
        if a + b != "AB" and apply(a + b, BAD) == apply("AB", BAD):
            print(a + b)
            raise SystemExit
munotes.in57

The Hill Cipher

key matrix K:
   17 17  5
   21 18 21
    2  2 19
det K mod 26 = 23 and its inverse mod 26 = 17
K inverse mod 26:
    4  9 15
   15 17  6
   24  0 17
plaintext : PAYMOREMONEY
ciphertext: LNSHDLEWMTRW
decrypted : PAYMOREMONEY

a key that cannot be used:
    6 24
    1 13
det = 2  gcd(det, 26) = 2  inverse = None
so this key encrypts and can never decrypt. Check it:
   AB encrypts to YN and so does NO

Read the second half of that run carefully, because it is the point of the chapter. The key with determinant 2 is a perfectly good-looking matrix. It encrypts happily. And AB and NO both come out as YN, so a receiver holding YN cannot know which was sent, and no amount of cleverness will help, because the information is gone. A Hill key whose determinant shares a factor with 26 is not weak; it is not a cipher at all.

munotes.in58

The Hill Cipher

Why the determinant must be coprime to 26

The inverse of a matrix modulo 26 is the modular inverse of its determinant, times the adjugate. So the inverse exists exactly when the determinant has a multiplicative inverse modulo 26, and a number has one exactly when it is coprime to 26. Since 26 is 2 times 13, the determinant must be odd and not a multiple of 13.

In the working key above the determinant is 23, which is coprime to 26, and its inverse is 17: you can check that 23 times 17 is 391, and 391 modulo 26 is 1, since 26 times 15 is 390.

A worked example on a 2 by 2, by hand

Take K as the matrix with first row 3, 3 and second row 2, 5, and encrypt HELP.

Step 1: the determinant. 3 times 5 minus 3 times 2 is 15 minus 6, which is 9. And 9 is coprime to 26, so the key can be used. The inverse of 9 modulo 26 is 3, because 9 times 3 is 27, which is 1 modulo 26.

Step 2: the first block, HE. H is 7 and E is 4.

  • First ciphertext value: 3 times 7 plus 3 times 4, which is 21 plus 12, which is 33; 33 modulo 26 is 7, which is H.
  • Second: 2 times 7 plus 5 times 4, which is 14 plus 20, which is 34; 34 modulo 26 is 8, which is I.

Step 3: the second block, LP. L is 11 and P is 15.

  • First: 3 times 11 plus 3 times 15, which is 33 plus 45, which is 78; 78 modulo 26 is 0, which is A.
  • Second: 2 times 11 plus 5 times 15, which is 22 plus 75, which is 97; 97 modulo 26 is 19, since 26 times 3 is 78 and 97 minus 78 is 19; 19 is T.

Ciphertext: HIAT.

Step 4: the inverse key. For a 2 by 2 matrix with entries a, b, c, d the adjugate has first row d, minus b and second row minus c, a. So here it is 5, minus 3 over minus 2, 3, which modulo 26 is 5, 23 over 24, 3. Multiply by 3, the inverse of the determinant: first row 15, 69 and second row 72, 9, which modulo 26 is 15, 17 over 20, 9.

Step 5: check. Decrypt HI: H is 7, I is 8. First: 15 times 7 plus 17 times 8, which is 105 plus 136, which is 241; 241 modulo 26 is 7, since 26 times 9 is 234; and 7 is H. Second: 20 times 7 plus 9 times 8, which is 140 plus 72, which is 212; 212 modulo 26 is 4, since 26 times 8 is 208; and 4 is E. So HI decrypts to HE, which is right.

munotes.in59

The Hill Cipher

The known-plaintext attack, worked

The setup. An attacker knows the cipher is Hill with m of 2, and has two plaintext and ciphertext block pairs. Say the attacker knows that HE encrypted to HI and LP encrypted to AT, which is exactly what the example above produced.

Step 1: write the pairs as matrices. Put the plaintext blocks as the columns of a matrix P and the matching ciphertext blocks as the columns of C:

  • P has first column 7, 4 (for H, E) and second column 11, 15 (for L, P).
  • C has first column 7, 8 (for H, I) and second column 0, 19 (for A, T).

Step 2: the relation. Since C = K P mod 26 for every column, the whole matrices satisfy the same equation. So if P is invertible modulo 26,

K = C P inverse mod 26

Step 3: invert P. Its determinant is 7 times 15 minus 11 times 4, which is 105 minus 44, which is 61; 61 modulo 26 is 9. The inverse of 9 modulo 26 is 3. The adjugate is 15, minus 11 over minus 4, 7, which is 15, 15 over 22, 7 modulo 26. Times 3: 45, 45 over 66, 21, which modulo 26 is 19, 19 over 14, 21.

Step 4: multiply. C times that inverse gives, entry by entry modulo 26: first row is 7 times 19 plus 0 times 14, which is 133, which is 3; and 7 times 19 plus 0 times 21, which is also 3. Second row: 8 times 19 plus 19 times 14, which is 152 plus 266, which is 418, and 418 modulo 26 is 2, since 26 times 16 is 416; and 8 times 19 plus 19 times 21, which is 152 plus 399, which is 551, and 551 modulo 26 is 5, since 26 times 21 is 546.

So K comes out with first row 3, 3 and second row 2, 5, which is the key. The attacker now has the whole key from four letters of known plaintext.

Step 5: what if P is not invertible? The attacker simply uses a different pair of blocks. Any two blocks whose plaintext matrix is invertible will do, and in a message of any length there are plenty.

munotes.in60

The Hill Cipher

The step that carries the marks. m squared known plaintext letters, arranged into an invertible matrix, give the whole key by one matrix inversion. Making m larger does not help, because the attacker only needs m squared letters and a longer message contains more of them. Linearity is the flaw, and no choice of key repairs it.

Distinctions that carry marks

CaesarPlayfairHill (m = 3)
Letters per unit123
Hides single-letter frequenciesnopartlycompletely
Hides digram frequenciesnonoyes
Ciphertext-only attacktriviala few hundred lettershard
Known-plaintext attacktrivialeasiertrivial, m squared letters
Keya numbera 5 by 5 squarean m by m matrix, invertible mod 26
Requirement on the keyWhy
Caesark not 0 or 26otherwise no change
Multiplicativea coprime to 26otherwise the map is not one to one
Hilldet K coprime to 26otherwise K has no inverse mod 26

The right-hand column of the second table is one idea in three dresses: a cipher must be a bijection, and each condition is what makes it one.

What beginners get wrong here

Forgetting to check the determinant. A matrix chosen at random has about a one in four chance of being unusable, because it needs to be both odd and not a multiple of 13. Always compute the determinant and its gcd with 26 first.

Taking the inverse over the real numbers. The inverse here is modulo 26, so the reciprocal of the determinant is its modular inverse, an integer, not a fraction. Writing one twenty-third is wrong.

Negative entries in the adjugate. Minus 3 modulo 26 is 23. Bring every entry into the range 0 to 25 before multiplying.

Claiming Hill is strong. It is strong against a ciphertext-only attacker and worthless against a known-plaintext one, and an answer must say which.

Confusing m with the key size. The key has m squared entries, so a 3 by 3 key is nine numbers, and the attacker needs nine known plaintext letters, not three.

Quick revision

  • Hill cipher, Lester Hill 1929: C = K P mod 26, P = K inverse C mod 26, blocks of m letters, key an m by m matrix.
  • The key must be invertible modulo 26, which means det K must be coprime to 26, so odd and not a multiple of 13.
  • The inverse is (modular inverse of the determinant) times the adjugate, all modulo 26.
  • Worked: K with rows 17 17 5, 21 18 21, 2 2 19 has determinant 23, inverse of determinant 17, and PAYMOREMONEY encrypts to LNSHDLEWMTRW.
  • A bad key is not weak but broken: with determinant 2, AB and NO both encrypt to YN.
  • Hill completely conceals single-letter frequencies for m of 3 or more.
  • It falls to a known-plaintext attack: K = C P inverse, needing m squared known plaintext letters.
  • The flaw is linearity, and a larger m does not fix it.
munotes.in61

The Hill Cipher

Test yourself

1. State the Hill cipher's encryption and decryption rules, and the condition on the key. C = K P mod 26 and P = K inverse C mod 26, where P and C are column vectors of m letter values and K is an m by m matrix. The key must be invertible modulo 26, which requires its determinant to be coprime to 26, that is odd and not a multiple of 13.

2. For the 2 by 2 key with rows 3, 3 and 2, 5, encrypt HELP. The determinant is 9, which is coprime to 26, so the key is usable. HE is 7, 4: the first value is 21 plus 12, which is 33, giving 7 or H; the second is 14 plus 20, which is 34, giving 8 or I. LP is 11, 15: the first is 33 plus 45, which is 78, giving 0 or A; the second is 22 plus 75, which is 97, giving 19 or T. The ciphertext is HIAT.

3. Find the inverse of that key modulo 26. The determinant is 9 and its inverse modulo 26 is 3, since 27 is 1 modulo 26. The adjugate is 5, minus 3 over minus 2, 3, which is 5, 23 over 24, 3 modulo 26. Multiplying by 3 gives 15, 69 over 72, 9, which reduces to 15, 17 over 20, 9.

4. Why must the determinant be coprime to 26, and what goes wrong if it is not? Because the matrix inverse modulo 26 requires the modular inverse of the determinant, which exists only for values coprime to 26. If it is not, the map is not one to one: distinct plaintext blocks collide on the same ciphertext block. Under the key with rows 6, 24 and 1, 13, whose determinant is 2, both AB and NO encrypt to YN, so decryption is impossible in principle.

5. Describe the known-plaintext attack on the Hill cipher. Collect m squared known plaintext letters, that is m block pairs. Arrange the plaintext blocks as the columns of a matrix P and the matching ciphertext blocks as the columns of C. Since C = K P mod 26, the key is K = C P inverse mod 26, provided P is invertible modulo 26; if it is not, choose different blocks. One matrix inversion recovers the whole key.

munotes.in62

The Hill Cipher

6. Why does increasing m not protect against that attack? Because the attacker needs m squared known plaintext letters, and any message long enough to be worth attacking contains far more than that. Increasing m strengthens the cipher only against a ciphertext-only attacker; the linear structure that makes the known-plaintext attack work is unchanged.

7. What does the Hill cipher achieve that Playfair does not? For m of 3 or more it completely conceals single-letter frequency information, because every ciphertext letter depends on three or more plaintext letters, and it conceals digram frequencies as well. Playfair flattens the single-letter frequencies but leaves digram structure that a few hundred letters of ciphertext is enough to exploit.

Contents This chapter on its own page

munotes.in63

Chapter Thirteen

Polyalphabetic Ciphers: Vigenere and the Autokey

Syllabus topic Module 1, "Classical Encryption Techniques: Substitution Techniques"

In one line

Use several cipher alphabets in rotation, chosen by the letters of a keyword. One plaintext letter now becomes different ciphertext letters in different places, so the letter frequencies of the language are flattened and frequency analysis no longer works directly.

In the wording a student can write in an examination: a polyalphabetic substitution cipher uses a set of related monoalphabetic substitution rules, with a key that determines which particular rule is applied for each letter of the plaintext. The Vigenere cipher is the best known: the key is a keyword repeated to the length of the message, and each plaintext letter is shifted by the value of the key letter above it, so

C = (p + k) mod 26

p = (C - k) mod 26

where k is the key letter's own value from A as 0 to Z as 25.

Why one alphabet is never enough

Because a monoalphabetic cipher is a relabelling, and relabelling preserves counts. E occurs about 12.7 times in a hundred letters of English, so whatever E becomes will occur about 12.7 times in a hundred letters of the ciphertext, and the attacker reads the key off the counts.

A polyalphabetic cipher breaks that correspondence. If the keyword is nine letters long, the E in position 1 is shifted by one amount and the E in position 2 by another, so E's 12.7 per cent is spread across up to nine different ciphertext letters. The ciphertext's letter counts flatten towards the 3.85 per cent that random text would give, and the single-letter attack has nothing to grip.

Encrypting

Write the keyword repeatedly above the plaintext, then add each pair modulo 26.

plaintext  W E A R E D I S C O V E R E D S A V E Y O U R S E L F
key        D E C E P T I V E D E C E P T I V E D E C E P T I V E
ciphertext Z I C V T W Q N G R Z G V T W A V Z H C Q Y G L M G J

Check the first column by hand: W is 22, D is 3, and 22 plus 3 is 25, which is Z. Check a wrap: the fourth column has R at 17 and E at 4, giving 21, which is V; the ninth has C at 2 and E at 4, giving 6, which is G. And a real wrap: the sixth column has D at 3 and T at 19, giving 22, which is W.

Classically this was done with the Vigenere tableau, a 26 by 26 table whose row i is the alphabet shifted by i. To encrypt, find the plaintext letter's column and the key letter's row, and read the ciphertext at the intersection. The tableau is only a lookup table for the addition, and it is worth saying so in an answer: the arithmetic is the cipher, the table is a convenience.

munotes.in64

Polyalphabetic Ciphers: Vigenere and the Autokey

Breaking it: find the key length first

A polyalphabetic cipher is a set of monoalphabetic ciphers interleaved. So if you know the key length, you have already won: take every ninth letter of the ciphertext and you have a monoalphabetic cipher on that column, which the chapter on frequency analysis broke. The whole attack therefore reduces to finding the key length.

There are two classical methods.

Kasiski's method. If two identical plaintext sequences happen to line up with the same part of the key, they produce identical ciphertext sequences. So look for repeated sequences of three or more letters in the ciphertext, measure the distances between them, and the key length is likely to be a common factor of those distances. In the example above, the sequence VTW appears twice, nine letters apart, and the key is nine letters long.

The index of coincidence. This one is more reliable and is what the program below uses. The index of coincidence of a text is the probability that two letters drawn at random from it are the same letter. For English it is about 0.0667, because English is lumpy. For a text whose letters are uniformly spread it is 1 in 26, which is about 0.0385.

Now the trick. Guess a key length m. Split the ciphertext into m columns, taking every mth letter. If the guess is right, each column was enciphered with a single shift, so each column is a monoalphabetic cipher on English and its index of coincidence should be close to English's 0.0667. If the guess is wrong, the columns mix several shifts and their index will be near 0.0385.

ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

def letters(s):
    return "".join(c for c in s.upper() if c in ALPHABET)

def vigenere(text, key, sign=1):
    t, k = letters(text), letters(key)
    return "".join(ALPHABET[(ALPHABET.index(t[i])
                             + sign * ALPHABET.index(k[i % len(k)])) % 26]
                   for i in range(len(t)))

def ic(s):
    t = letters(s)
    n = len(t)
    if n < 2:
        return 0.0
    return sum(t.count(c) * (t.count(c) - 1) for c in set(t)) / (n * (n - 1))

plain = "wearediscoveredsaveyourself"
key = "deceptive"
cipher = vigenere(plain, key)
print("plaintext :", letters(plain))
print("key       :", (letters(key) * 4)[:len(letters(plain))])
print("ciphertext:", cipher)
print("decrypted :", vigenere(cipher, key, -1))
print()

long_plain = ("the examination section will publish the seat numbers for the theory "
              "papers on the college notice board and on the college website before "
              "the end of this month so that every student can check the hall and the "
              "date of each paper well before the examination begins")
long_cipher = vigenere(long_plain, "deceptive")
print("index of coincidence of the plaintext :", "%.4f" % ic(long_plain))
print("index of coincidence of the ciphertext:", "%.4f" % ic(long_cipher))
print("English is about 0.0667, a flat random text about 0.0385")
print()
print("average index of coincidence of the columns, for each assumed key length:")
best = []
for m in range(1, 13):
    cols = [long_cipher[i::m] for i in range(m)]
    avg = sum(ic(c) for c in cols) / m
    best.append((avg, m))
    print("   length %2d  %.4f" % (m, avg))
best.sort(reverse=True)
print()
print("the three highest:", ", ".join("%d (%.4f)" % (m, a) for a, m in best[:3]))
print("the real key length is", len("deceptive"))
munotes.in65

Polyalphabetic Ciphers: Vigenere and the Autokey

plaintext : WEAREDISCOVEREDSAVEYOURSELF
key       : DECEPTIVEDECEPTIVEDECEPTIVE
ciphertext: ZICVTWQNGRZGVTWAVZHCQYGLMGJ
decrypted : WEAREDISCOVEREDSAVEYOURSELF

index of coincidence of the plaintext : 0.0736
index of coincidence of the ciphertext: 0.0409
English is about 0.0667, a flat random text about 0.0385

average index of coincidence of the columns, for each assumed key length:
   length  1  0.0409
   length  2  0.0395
   length  3  0.0473
   length  4  0.0397
   length  5  0.0401
   length  6  0.0444
   length  7  0.0393
   length  8  0.0402
   length  9  0.0717
   length 10  0.0392
   length 11  0.0419
   length 12  0.0419

the three highest: 9 (0.0717), 3 (0.0473), 6 (0.0444)
the real key length is 9

Read four things out of that run.

The cipher did its job. The plaintext's index of coincidence is 0.0736, a little above English's average because this passage repeats words like "the" and "examination". The ciphertext's is 0.0409, close to flat random. Frequency analysis on the ciphertext as a whole is dead.

Key length 9 stands out, and by a wide margin. 0.0717 against 0.0473 for the next best. This is what makes the method practical: you do not need judgement, you take the largest.

The second best is a divisor of the answer, and that is not a coincidence. Length 3 scores 0.0473 because 3 divides 9: splitting into three columns groups key positions 1, 4 and 7 together, so each column mixes three shifts rather than nine and is lumpier than random even though the guess is wrong. So a divisor of the true length scores above the ordinary run, and the rule for reading the table is to take the largest good score, not the first: if 3 were taken as the answer, 9 would be missed. Length 6 at 0.0444 is not a divisor of 9 and is simply noise, which is the honest caution: on a sample of 216 letters the low scores sit between 0.0392 and 0.0444 and a single value in that band means nothing.

munotes.in66

Polyalphabetic Ciphers: Vigenere and the Autokey

0.0717 is above English's 0.0667, which looks wrong and is not. Each column is a monoalphabetic cipher on a sample of only twenty-four letters, and the index of coincidence of a small sample is noisy. The method finds the key length; it does not measure English precisely.

After the key length: recovering the key

Once the length is 9, take column 0, which is every ninth ciphertext letter starting at the first. It was produced by shifting English by one fixed amount, so run the Caesar break of the earlier chapter on it: try all 26 shifts and take the one whose letter frequencies fit English best. That gives the first letter of the keyword. Repeat for the other eight columns. The whole key falls out, and with it the message.

This is why the length of the keyword matters so much. A keyword of length 9 on the 216-letter message above gives each column 24 letters, which is thin but workable. A keyword as long as the message gives each column one letter, and then there is nothing to count, which is the one-time pad of the next chapter.

The autokey cipher: the improvement that does not work

Vigenere himself proposed a better scheme. The trouble with a repeating keyword is exactly that it repeats, so use the plaintext itself to extend the key: start with the keyword and then continue with the plaintext.

key        D E C E P T I V E W E A R E D I S C O V E R E D S A V
plaintext  W E A R E D I S C O V E R E D S A V E Y O U R S E L F
ciphertext Z I C V T W Q N G K Z E I I G A S X S T S L V V W L A

There is now no period at all, so Kasiski's method and the index of coincidence both have nothing to find. And the cipher is still broken, because the key is English. The attacker guesses a short piece of key, decrypts a few letters, and if the result is English then those plaintext letters are also key letters further along, which decrypt more, which give more key. The attack propagates through the message from any correct guess. The statistical structure of the plaintext leaked into the key, and that is fatal.

The lesson is worth an answer of its own: removing the period is not enough. The key must also be unpredictable.

Distinctions that carry marks

MonoalphabeticVigenereAutokeyOne-time pad
Cipher alphabets used1as many as the keyword is longno repeat at allno repeat at all
Keya permutationa keyworda keyword, then the plaintextrandom, as long as the message
Period1the keyword lengthnonenone
Key is unpredictableirrelevantnono, it is Englishyes
Broken byletter frequenciesKasiski or the index of coincidence, then per columnguess a little key and propagatenothing
munotes.in67

Polyalphabetic Ciphers: Vigenere and the Autokey

Kasiski's methodIndex of coincidence
Looks forrepeated sequences of three or more lettershow lumpy each column's letters are
Givesthe key length as a common factor of the distancesthe key length as the best-scoring guess
Needsa lucky repetitiononly enough ciphertext
Reliabilitygood when repetitions occurbetter, and mechanical

What beginners get wrong here

Thinking Vigenere is unbreakable. It was called le chiffre indechiffrable for three hundred years and it is broken by a schoolchild with a spreadsheet. Any answer claiming it is secure is wrong.

Forgetting to find the key length first. The attack is two stages, and the first stage is the whole difficulty. An answer that jumps to "then use frequency analysis" has skipped the part being examined.

Reading the index of coincidence backwards. A high index means lumpy, English-like text, so a high score means the guessed length is right. Low means flat and wrong.

Taking the smallest good score as the key length. A divisor of the true length scores well too, so take the largest clear winner and treat a smaller good score as a hint that its multiple is the answer.

Thinking the autokey cipher is a real improvement. It removes the period and keeps the predictability, which is the wrong half of the problem to solve.

Quick revision

  • Polyalphabetic: several cipher alphabets, selected by the key. Vigenere: C = (p + k) mod 26 with k the key letter, keyword repeated.
  • The keyword's length is the period, and the ciphertext's letter frequencies are flattened towards 1 in 26.
  • Break in two stages: find the key length, then solve each column as a Caesar cipher.
  • Kasiski: repeated three-letter sequences; the key length divides the distances between them.
  • Index of coincidence: the chance two random letters are equal. English about 0.0667, flat text about 0.0385. Split into m columns and take the m with the highest average.
  • In the run, the true length 9 scored 0.0717 and its divisor 3 scored 0.0473, so take the largest clear winner; the other low scores sat between 0.0392 and 0.0444 and mean nothing individually.
  • Autokey: keyword then plaintext, no period, still broken, because the key is English. Removing the period is not enough: the key must be unpredictable.
munotes.in68

Polyalphabetic Ciphers: Vigenere and the Autokey

Test yourself

1. Define a polyalphabetic cipher and give the Vigenere rule. A cipher that uses a set of related monoalphabetic substitution rules, with the key selecting which rule applies at each position. In Vigenere the key is a keyword repeated to the message length, and each plaintext letter is shifted by the value of the key letter above it: C = (p + k) mod 26, with decryption p = (C - k) mod 26.

2. Encrypt CIPHER with the keyword KEY. The key stream is K E Y K E Y, that is 10, 4, 24, 10, 4, 24. C is 2 giving 12, which is M. I is 8 giving 12, which is M. P is 15; 15 plus 24 is 39; 39 modulo 26 is 13, which is N. H is 7 giving 17, which is R. E is 4 giving 8, which is I. R is 17; 17 plus 24 is 41; 41 modulo 26 is 15, which is P. The ciphertext is MMNRIP.

3. Why does Vigenere defeat simple frequency analysis? Because one plaintext letter is enciphered with different shifts at different positions, so its frequency is spread over as many ciphertext letters as the key is long. The ciphertext's letter distribution flattens towards uniform, and the correspondence between a ciphertext letter's count and a plaintext letter is destroyed.

4. What is the index of coincidence, and what are its values for English and for random text? The probability that two letters drawn at random from a text are the same letter. For English it is about 0.0667; for text whose letters are uniformly distributed it is 1 in 26, about 0.0385.

5. How is the index of coincidence used to find the key length? Guess a length m, split the ciphertext into m columns by taking every mth letter, and average the index of coincidence of the columns. If m is right each column is a single-shift cipher on English and the average is high; if m is wrong the columns mix shifts and the average is near 0.0385. The best-scoring m is the key length, and its divisors also score above the rest, which confirms the answer.

6. Describe Kasiski's method. Find sequences of three or more letters that repeat in the ciphertext. Such a repetition usually arises because the same plaintext lined up with the same part of the key, so the distance between the two occurrences is a multiple of the key length. Taking the common factors of several such distances gives the key length.

7. What is the autokey cipher, and why is it still broken? The key is the keyword followed by the plaintext itself, so the key never repeats and there is no period for Kasiski or the index of coincidence to find. It is broken because the key is ordinary English and therefore predictable: a correct guess at a short stretch of key reveals plaintext, and that plaintext is itself key material further along, so a single correct guess propagates through the whole message.

Contents This chapter on its own page

munotes.in69

Chapter Fourteen

The One-Time Pad, and What Perfect Secrecy Means

Syllabus topic Module 1, "Classical Encryption Techniques: Substitution Techniques"

In one line

A key as long as the message, truly random, and never used again. Under those three conditions the cipher cannot be broken, ever, by anybody, with any amount of computing. Break any one of the three and it collapses.

In the wording a student can write in an examination: Gilbert Vernam's cipher of 1917 combines the plaintext with a key stream of the same length, bit by bit, using the exclusive-or operation. Joseph Mauborgne proposed that the key be a random stream as long as the message and used only once, which produces the one-time pad. Claude Shannon proved in 1949 that the one-time pad achieves perfect secrecy: the ciphertext gives the cryptanalyst no information whatever about the plaintext, so the cipher is unconditionally secure. It is the only cipher on this syllabus of which that is true.

The two forms

The letter form, which is what a written examination will ask you to compute. Number the letters A as 0 to Z as 25, and add the key letter to the plaintext letter modulo 26. To decrypt, subtract.

The bit form, which is the Vernam cipher proper and what every real system uses.

c = p XOR k

p = c XOR k

The reason the bit form is so convenient is that exclusive-or is its own inverse: applying the key twice returns the plaintext, because any bit exclusive-ored with itself is 0 and any bit exclusive-ored with 0 is unchanged. So the encryption and decryption programs are the same program, which is not true of any other cipher in this book.

The proof, performed

Perfect secrecy means something very precise: for any ciphertext, every plaintext of the same length is equally possible. The attacker who holds the ciphertext is in exactly the position they were in before they held it.

Here is what that means concretely. Take the ciphertext and any plaintext you like of the same length. There is a pad that makes the one decrypt to the other, and it is found by subtracting. So the ciphertext is consistent with every message, and choosing between them is guessing.

ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

def letters(s):
    return "".join(c for c in s.upper() if c in ALPHABET)

def add(text, key):
    t, k = letters(text), letters(key)
    return "".join(ALPHABET[(ALPHABET.index(t[i]) + ALPHABET.index(k[i])) % 26]
                   for i in range(len(t)))

def sub(text, key):
    t, k = letters(text), letters(key)
    return "".join(ALPHABET[(ALPHABET.index(t[i]) - ALPHABET.index(k[i])) % 26]
                   for i in range(len(t)))

plain = "ATTACKATDAWN"
pad = "XMCKLXMCKLAB"
cipher = add(plain, pad)
print("plaintext :", plain)
print("pad       :", pad)
print("ciphertext:", cipher)
print("decrypted :", sub(cipher, pad))
print()

print("the proof of perfect secrecy: one ciphertext, two plausible plaintexts")
for wanted in ("RETREATATTEN", "SENDTANKSNOW"):
    fake = sub(cipher, wanted)
    print("   a pad of %s turns %s into %s" % (fake, cipher, sub(cipher, fake)))
print()
print("both pads are the same length and look equally random, so the ciphertext")
print("cannot tell an attacker which one was used.")
print()

reused = add("ATTACKATDAWN", "KEYKEYKEYKEY")
reused2 = add("HOLDPOSITION", "KEYKEYKEYKEY")
print("what a reused pad gives away:")
print("   c1 =", reused)
print("   c2 =", reused2)
diff = "".join(ALPHABET[(ALPHABET.index(a) - ALPHABET.index(b)) % 26]
               for a, b in zip(reused, reused2))
print("   c1 - c2 =", diff, "which is p1 - p2 and has no key in it at all")
check = "".join(ALPHABET[(ALPHABET.index(a) - ALPHABET.index(b)) % 26]
                for a, b in zip("ATTACKATDAWN", "HOLDPOSITION"))
print("   p1 - p2 =", check, "so the two agree:", diff == check)
munotes.in70

The One-Time Pad, and What Perfect Secrecy Means

plaintext : ATTACKATDAWN
pad       : XMCKLXMCKLAB
ciphertext: XFVKNHMVNLWO
decrypted : ATTACKATDAWN

the proof of perfect secrecy: one ciphertext, two plausible plaintexts
   a pad of GBCTJHTVUSSB turns XFVKNHMVNLWO into RETREATATTEN
   a pad of FBIHUHZLVYIS turns XFVKNHMVNLWO into SENDTANKSNOW

both pads are the same length and look equally random, so the ciphertext
cannot tell an attacker which one was used.

what a reused pad gives away:
   c1 = KXRKGIKXBKAL
   c2 = RSJNTMCMRSSL
   c1 - c2 = TFIXNWILKSIA which is p1 - p2 and has no key in it at all
   p1 - p2 = TFIXNWILKSIA so the two agree: True

Read the middle block of that run again, because it is the whole chapter. One ciphertext, XFVKNHMVNLWO. Three different twelve-letter military orders, all consistent with it, each with its own perfectly ordinary-looking pad. An attacker who captures the ciphertext and knows that it is a one-time pad knows the length of the message and nothing else. There is no computation that helps, because the information is not there. Contrast every other cipher in this book, where the right key produces English and the wrong keys produce rubbish: that difference is exactly what a cryptanalyst exploits, and the one-time pad does not offer it.

Why it is almost never usable

The one-time pad is unbreakable and is used for almost nothing. The three conditions are the reason.

The key must be as long as the message. To send a megabyte you must first have securely delivered a megabyte of key. But if you had a secure channel for a megabyte of key, you could have sent the message down it. The cipher does not remove the need for a secure channel, it only moves it in time, and that is useful only when a courier can carry the key in advance.

The key must be truly random. Not the output of a program, which is predictable to anybody who knows the program and its starting state, but physically random: radioactive decay, thermal noise, electronic noise. A pad generated by a random number generator is not a one-time pad, it is a stream cipher, and stream ciphers are computationally secure at best.

munotes.in71

The One-Time Pad, and What Perfect Secrecy Means

The key must never be used twice. The last block of the run shows what happens if it is. Subtract the two ciphertexts and the key cancels exactly, leaving the difference of the two plaintexts, which is a cipher with no key at all and falls to ordinary analysis. c1 - c2 came out TFIXNWILKSIA and so did p1 - p2: the program checked that the two are equal, and they are.

That failure is not hypothetical. Reusing key material is one of the most productive real attacks there has ever been against systems that were otherwise sound, and it is exactly the mistake that broke WEP's use of RC4, which the chapter on stream ciphers takes up.

Where it is genuinely used: a diplomatic or military link where a courier can deliver key material in advance, and the volume of traffic is small. Historically, the Moscow to Washington hot line. Nothing on a campus network qualifies.

A worked example in the bit form

Encrypt the byte 01001000, which is the letter H in ASCII, with the key byte 11010110.

Line them up and exclusive-or bit by bit. Exclusive-or gives 1 when the two bits differ and 0 when they are the same.

plaintext  0 1 0 0 1 0 0 0
key        1 1 0 1 0 1 1 0
ciphertext 1 0 0 1 1 1 1 0

Decrypt by applying the same key to the ciphertext.

ciphertext 1 0 0 1 1 1 1 0
key        1 1 0 1 0 1 1 0
plaintext  0 1 0 0 1 0 0 0

The plaintext is back, and the second table is the same operation as the first. That is what "exclusive-or is its own inverse" buys, and it is why every stream cipher in this book, and the counter and output feedback modes of block ciphers, all end in an exclusive-or with a key stream.

Distinctions that carry marks

Vernam cipherOne-time pad
Combines plaintext with a key streamyesyes
Key streammay repeat, may be generatedrandom, as long as the message, used once
Securitycomputational at bestunconditional
Due toVernam, 1917Mauborgne's addition to it
Computationally secureUnconditionally secure
Breaking itcosts more than the data is worth, or takes longer than it mattersis impossible, whatever the resources
Depends onthe cost of computing, so on a datenothing
ExampleDES, AES, RSA, Vigenerethe one-time pad only
Proofnone; only the absence of a known attackShannon, 1949
munotes.in72

The One-Time Pad, and What Perfect Secrecy Means

Vigenere with a long keyOne-time pad
Key lengthshorter than the messageequal to the message
Key repeatsyesnever
Key randomno, a wordyes
Columns available to the attackerseveral letters eachone letter each
Broken byindex of coincidence, then per columnnothing

The third table is the most useful one, because it shows that the one-time pad is not a different kind of cipher from Vigenere at all. It is Vigenere with the period pushed out to the length of the message and the key made random, and that alone takes it from breakable to unbreakable.

What beginners get wrong here

"Unbreakable" is a claim about information, not about difficulty. The one-time pad is not hard to break; it is impossible, because the ciphertext does not determine the plaintext. Saying "it would take too long" describes AES, not this.

A pad generated by software is not a one-time pad. It is a stream cipher whose key is the seed. The security then rests on the generator, which is a computational assumption.

Perfect secrecy says nothing about integrity. An attacker who cannot read a one-time pad ciphertext can still flip a bit of it, and the corresponding bit of the plaintext flips too. Worse, an attacker who knows the plaintext can change it to any other plaintext of the same length by exclusive-oring in the difference. The pad gives confidentiality alone, and this is why authenticated encryption exists.

The key distribution problem is not solved, only moved. The pad needs a secure channel for as many bits as the message. That is why public-key cryptography had to be invented.

Quick revision

  • Vernam, 1917: c = p XOR k. One-time pad: Vernam with a key that is random, as long as the message, and used once.
  • Shannon, 1949: the one-time pad has perfect secrecy and is unconditionally secure. It is the only such cipher here.
  • Perfect secrecy means every plaintext of the same length is equally consistent with the ciphertext; the run produced the pads for two different plausible messages from one ciphertext.
  • Exclusive-or is its own inverse, so encryption and decryption are the same operation.
  • Reusing a pad: c1 - c2 equals p1 - p2, the key cancels, and both are gone. Proved in the run.
  • Not usable in general because the key is as long as the message, must be physically random, and must never repeat.
  • It gives confidentiality only. Flip a ciphertext bit and the plaintext bit flips; knowing the plaintext lets an attacker substitute any other of the same length.
  • Used where a courier can deliver key in advance and traffic is small.
munotes.in73

The One-Time Pad, and What Perfect Secrecy Means

Test yourself

1. What are the three conditions for a one-time pad, and what do they buy? The key must be truly random, at least as long as the message, and used only once. Together they give perfect secrecy: the ciphertext carries no information about the plaintext, so the cipher is unconditionally secure and cannot be broken by any amount of computation.

2. State what perfect secrecy means, and how it can be demonstrated. That for a given ciphertext every plaintext of the same length is equally possible, so holding the ciphertext leaves the attacker exactly as informed as before. It is demonstrated by taking one ciphertext and, for any chosen plaintext of the same length, producing the key that makes the one decrypt to the other: subtract the plaintext from the ciphertext. Since every such key is as plausible as any other, the ciphertext cannot distinguish the messages.

3. Encrypt the bits 10110010 with the key 01101001 and then decrypt the result. Exclusive-or gives 11011011. Applying the same key to that gives 10110010, the original, because exclusive-or is its own inverse.

4. What happens if a one-time pad is used twice, and why? Subtracting the two ciphertexts removes the key entirely, because the same key was added to both, and leaves the difference of the two plaintexts. That difference is a cipher with no key, and can be attacked by ordinary language analysis. The run in this chapter checked it: c1 - c2 and p1 - p2 came out identically as TFIXNWILKSIA.

5. Why is the one-time pad rarely used despite being unbreakable? Because the key must be as long as the message and delivered securely in advance, so it does not remove the need for a secure channel but only shifts it in time; and because a truly random key must come from a physical source rather than from a program. It is practical only where key material can be couriered ahead and traffic volumes are small.

6. Is a stream cipher with a pseudorandom key stream a one-time pad? Justify. No. The key stream is generated from a short seed by a deterministic algorithm, so it is predictable to anybody who learns the seed and the algorithm, and the number of possible key streams is limited by the number of seeds. Security then rests on a computational assumption about the generator, which makes the cipher computationally secure at best rather than unconditionally secure.

7. Does a one-time pad protect integrity? Explain with an example. No. It gives confidentiality only. Flipping any bit of the ciphertext flips the corresponding bit of the recovered plaintext, and an attacker who knows the plaintext can exclusive-or in the difference between it and any other message of the same length, so the recipient decrypts to whatever the attacker chose. Integrity therefore needs a separate mechanism such as a message authentication code.

Contents This chapter on its own page

munotes.in74

Chapter Fifteen

Transposition Techniques: Rail Fence and Row Transposition

Syllabus topic Module 1, "Classical Encryption Techniques: Transposition Techniques"

In one line

Keep the letters and move them. A transposition cipher performs a permutation on the positions of the plaintext letters, changing no letter into any other, so the ciphertext contains exactly the same letters as the plaintext in a different order.

In the wording a student can write in an examination: a transposition cipher, also called a permutation cipher, achieves its effect by performing some sort of permutation on the plaintext letters. Unlike a substitution cipher it does not replace one symbol by another, so the letter frequencies of the ciphertext are identical to those of the plaintext, and a transposition is recognised by that very fact.

Why a transposition is a different idea from a substitution

Substitution and transposition are the two elementary operations out of which every cipher in this book is built, including DES and AES. It is worth naming the difference precisely, because the rest of Module 1 depends on it.

A substitution changes the symbols and keeps the positions. A transposition changes the positions and keeps the symbols.

Neither is enough on its own. A substitution leaves the letter frequencies in place, relabelled, and a transposition leaves them in place unrelabelled, which is worse. But a substitution followed by a transposition, repeated many times, is very strong indeed, and that is exactly what a product cipher is and what the Feistel structure of the next chapters implements. Shannon's two words for what they achieve are confusion, which substitution provides, and diffusion, which transposition provides.

The rail fence cipher

The simplest transposition, and the one most often set because it can be drawn.

The method. Write the plaintext downwards and diagonally over a number of rows, called the depth, going down until the bottom row and then back up, like a zigzag along a fence. Then read the ciphertext off row by row.

The key is the depth. That is all, which is why the cipher is weak: the depth is a small number and an attacker tries them in order.

The program below does depth 2 and depth 3 on the same plaintext, prints the rows so the zigzag is visible, and also proves the letter-count property.

ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

def letters(s):
    return "".join(c for c in s.upper() if c in ALPHABET)

def rail_fence(plain, depth):
    t = letters(plain)
    rows = [""] * depth
    r, step = 0, 1
    for c in t:
        rows[r] += c
        if r == 0:
            step = 1
        elif r == depth - 1:
            step = -1
        r += step
    return rows, "".join(rows)

def row_transposition(plain, key, pad="X"):
    order = [int(d) for d in key]
    n = len(order)
    t = letters(plain)
    t += pad * (-len(t) % n)
    grid = [t[i:i + n] for i in range(0, len(t), n)]
    out = ""
    for k in range(1, n + 1):
        col = order.index(k)
        out += "".join(row[col] for row in grid)
    return grid, out

plain = "meetmeafterthetogaparty"
rows, cipher = rail_fence(plain, 2)
print("rail fence, depth 2")
print("   plaintext:", letters(plain))
for i, r in enumerate(rows):
    print("   row %d    : %s" % (i, " ".join(r)))
print("   ciphertext:", cipher)
print()

rows3, cipher3 = rail_fence(plain, 3)
print("rail fence, depth 3")
for i, r in enumerate(rows3):
    print("   row %d    : %s" % (i, " ".join(r)))
print("   ciphertext:", cipher3)
print()

key = "4312567"
grid, cipher_rt = row_transposition("attackpostponeduntiltwoam", key)
print("row transposition, key", key)
print("   key  :", " ".join(key))
for row in grid:
    print("   row  :", " ".join(row))
print("   read the columns in the order 1 2 3 4 5 6 7:")
print("   ciphertext:", cipher_rt)
print()

_, twice = row_transposition(cipher_rt, key)
print("the same transposition applied twice:", twice)
print()

def counts(s):
    return {c: s.count(c) for c in sorted(set(s))}
p = letters(plain)
print("a transposition preserves the letter counts exactly:")
print("   plaintext :", counts(p))
print("   ciphertext:", counts(cipher))
print("   equal:", counts(p) == counts(cipher))
munotes.in75

Transposition Techniques: Rail Fence and Row Transposition

rail fence, depth 2
   plaintext: MEETMEAFTERTHETOGAPARTY
   row 0    : M E M A T R H T G P R Y
   row 1    : E T E F E T E O A A T
   ciphertext: MEMATRHTGPRYETEFETEOAAT

rail fence, depth 3
   row 0    : M M T H G R
   row 1    : E T E F E T E O A A T
   row 2    : E A R T P Y
   ciphertext: MMTHGRETEFETEOAATEARTPY

row transposition, key 4312567
   key  : 4 3 1 2 5 6 7
   row  : A T T A C K P
   row  : O S T P O N E
   row  : D U N T I L T
   row  : W O A M X X X
   read the columns in the order 1 2 3 4 5 6 7:
   ciphertext: TTNAAPTMTSUOAODWCOIXKNLXPETX

the same transposition applied twice: NSCXAUOPTTWLTMDNAOIEPAXTTOKX

a transposition preserves the letter counts exactly:
   plaintext : {'A': 3, 'E': 5, 'F': 1, 'G': 1, 'H': 1, 'M': 2, 'O': 1, 'P': 1, 'R': 2, 'T': 5, 'Y': 1}
   ciphertext: {'A': 3, 'E': 5, 'F': 1, 'G': 1, 'H': 1, 'M': 2, 'O': 1, 'P': 1, 'R': 2, 'T': 5, 'Y': 1}
   equal: True

Read four things out of that run.

Depth 3 does not fill the rows evenly. Row 1, the middle row, gets eleven letters while rows 0 and 2 get six each, because the zigzag passes through the middle on the way down and again on the way up. A student drawing this by hand must count the letters per row before reading them off, or the ciphertext will be wrong.

munotes.in76

Transposition Techniques: Rail Fence and Row Transposition

The row transposition grid needed padding. attackpostponeduntiltwoam is twenty-five letters and the key has seven columns, so three X's were added. Say so in an answer: the padding is part of the method.

The ciphertext is read column by column, in the order the key gives. The key 4312567 means the first column is labelled 4, the second 3, the third 1, and so on. So the third column is read first, because it is labelled 1. That is the step that goes wrong: the key gives each column a label, and you read in label order, not in key order.

The letter counts are equal, exactly. The two dictionaries are identical. This is the identifying signature of a transposition and the answer to "how would you recognise a transposition cipher from its ciphertext".

Row transposition, by hand

Encrypt ATTACK POSTPONED UNTIL TWO AM with the key 4312567.

Step 1: write the key across the top and the plaintext in rows beneath it, seven letters to a row.

key         4 3 1 2 5 6 7
row 1       A T T A C K P
row 2       O S T P O N E
row 3       D U N T I L T
row 4       W O A M X X X

Step 2: pad. Twenty-five letters into rows of seven leaves the last row three short, so three X's are added. The grid is now full.

Step 3: read the columns in the order of their labels. Column labelled 1 is the third column, reading down: T, T, N, A. Column 2 is the fourth: A, P, T, M. Column 3 is the second: T, S, U, O. Column 4 is the first: A, O, D, W. Column 5 is the fifth: C, O, I, X. Column 6 is the sixth: K, N, L, X. Column 7 is the seventh: P, E, T, X.

Ciphertext: TTNA APTM TSUO AODW COIX KNLX PETX, which run together is TTNAAPTMTSUOAODWCOIXKNLXPETX, exactly what the program printed.

To decrypt, do it backwards: the ciphertext length divided by the number of columns gives the number of rows, four here; cut the ciphertext into four-letter pieces; write the first piece down the column labelled 1, the second down the column labelled 2, and so on; then read the grid across.

Breaking a transposition

Step 1: recognise it. Count the letters. If the frequencies match the language's, it is a transposition and not a substitution. That single test decides which family of attacks to use, and it is why the program prints the counts.

Step 2: guess the block length. Try each possible number of columns. The correct one is usually recognisable because plausible digrams and trigrams start appearing when the columns are correctly grouped.

munotes.in77

Transposition Techniques: Rail Fence and Row Transposition

Step 3: rearrange. With the block length known, the problem is to find the permutation, and the tool is digram and trigram frequency: try orders of the columns and score the result by how many common English digrams it produces. For a seven-column key there are only 5,040 orders, so this is a small search.

Step 4: if it is a double transposition, the work is much greater, which is why double transposition was used seriously in both World Wars. The program applied the same key twice and got NSCXAUOPTTWLTMDNAOIEPAXTTOKX, which is not the plaintext and not the single ciphertext either: composing a permutation with itself gives another permutation, and a much less structured one. A single transposition leaves letters that were adjacent in the plaintext still fairly close together in the ciphertext; a double one does not, which is exactly the diffusion the attacker was relying on.

Distinctions that carry marks

SubstitutionTransposition
Changesthe symbolsthe positions
Keepsthe positionsthe symbols
Letter frequencies in the ciphertextthose of the plaintext, relabelledidentical to the plaintext's
Recognised byfrequencies matching a shifted or relabelled profilefrequencies matching the language exactly
Shannon's termconfusiondiffusion
Example hereCaesar, Playfair, Hill, Vigenererail fence, row transposition
Rail fenceRow transposition
Keythe depth, a small numbera permutation of the columns
Keyspacea handful of depthsthe factorial of the number of columns
Written asa zigzag over rowsa rectangle
Padding needednoyes, usually
Broken bytrying each depthdigram analysis over the column orders

What beginners get wrong here

Reading the columns in the wrong order. The key labels the columns; you read in label order. With key 4312567 the first column read is the third one.

Forgetting to pad, or forgetting to say you padded. An unfilled last row makes the columns unequal and the decryption ambiguous.

Miscounting the rail fence rows at depth 3 or more. The middle rows hold about twice as many letters as the top and bottom rows.

Thinking a transposition hides anything about letter frequencies. It hides nothing at all about them, which is a complete giveaway and the reason transposition alone is never used.

Assuming a double transposition with the same key undoes itself. It does not. A permutation composed with itself is a different permutation, and the run shows the result.

Quick revision

  • Transposition: permute the positions, change no symbol. Letter frequencies are identical to the plaintext's, which is how you recognise one.
  • Rail fence: write in a zigzag over depth rows, read off row by row. The key is the depth, so the keyspace is tiny.
  • Row transposition: write in rows of n, read the columns in the order the key labels them. Pad the last row.
  • With key 4312567, ATTACKPOSTPONEDUNTILTWOAM becomes TTNAAPTMTSUOAODWCOIXKNLXPETX.
  • Decrypt by computing the number of rows, cutting the ciphertext into that many letters per piece, and writing each piece down its labelled column.
  • Break it by recognising the frequencies, guessing the block length, then searching column orders with digram scores.
  • Double transposition is much stronger and was used in both World Wars; applying the same key twice gives a new permutation, not the identity.
  • Substitution gives confusion, transposition gives diffusion, and a strong cipher needs both.
munotes.in78

Transposition Techniques: Rail Fence and Row Transposition

Test yourself

1. Define a transposition cipher and state how a ciphertext reveals that one was used. It permutes the positions of the plaintext symbols without replacing any symbol by another. Because no symbol changes, the letter frequencies of the ciphertext are exactly those of the plaintext, so a ciphertext whose letter counts match the language's ordinary profile is the product of a transposition rather than a substitution.

2. Encrypt DEFEND THE EAST WALL with a rail fence of depth 2. The letters are DEFENDTHEEASTWALL. Row 0 takes the letters in odd positions, D, F, N, T, E, A, T, A, L, and row 1 the rest, E, E, D, H, E, S, W, L. Reading row 0 then row 1 gives DFNTEATALEEDHESWL.

3. Encrypt ATTACK AT DAWN with the row transposition key 31452. The plaintext is ATTACKATDAWN, twelve letters, and the key has five columns, so pad to fifteen with three X's: rows are ATTAC, KATDA, WNXXX. Column labelled 1 is the second: T, A, N. Label 2 is the fifth: C, A, X. Label 3 is the first: A, K, W. Label 4 is the third: T, T, X. Label 5 is the fourth: A, D, X. The ciphertext is TANCAXAKWTTXADX.

4. How is a row transposition decrypted? Divide the ciphertext length by the number of columns to get the number of rows. Cut the ciphertext into consecutive pieces of that length. Write the first piece down the column labelled 1, the second down the column labelled 2, and so on until the grid is full, then read the grid row by row and discard any padding.

5. Why is a transposition never used on its own, and what is it used for instead? Because it preserves letter frequencies completely, so it conceals nothing from a frequency analysis and only the ordering has to be recovered. It is used as one of the two elementary operations inside a product cipher, where it supplies diffusion, spreading the influence of each plaintext symbol over many ciphertext positions, while substitution supplies confusion.

munotes.in79

Transposition Techniques: Rail Fence and Row Transposition

6. What is a double transposition, and why is it much stronger? Applying a transposition twice, with the same or a different key. It is stronger because a single transposition leaves letters that were adjacent in the plaintext fairly close together in the ciphertext, which an attacker exploits; composing two permutations destroys that locality, so the structure the attack depends on is gone. Applying the key 4312567 twice to ATTACKPOSTPONEDUNTILTWOAM gives NSCXAUOPTTWLTMDNAOIEPAXTTOKX.

7. Distinguish confusion from diffusion and say which elementary operation provides each. Confusion makes the relationship between the key and the ciphertext as complex as possible, and is provided by substitution. Diffusion spreads the statistical structure of the plaintext over the whole ciphertext, so that a change in one plaintext symbol affects many ciphertext symbols, and is provided by transposition. A strong cipher alternates the two over many rounds.

Contents This chapter on its own page

munotes.in80

Chapter Sixteen

Steganography: Hiding That There Is a Message at All

Syllabus topic Module 1, "Classical Encryption Techniques: Steganography"

In one line

Encryption hides what the message says. Steganography hides that there is a message. An opponent who sees a cipher knows something secret is being sent; an opponent who sees a holiday photograph does not.

In the wording a student can write in an examination: steganography is the practice of concealing the existence of a message, in contrast to cryptography, which conceals its contents. A plaintext message is hidden inside an innocuous-looking cover, so that a third party observing the cover has no reason to suspect that any communication is taking place.

Why anybody would use it

Because sometimes the dangerous fact is not the content but the traffic. Three situations make the distinction real.

Where encryption is forbidden or suspicious. If the use of encryption is itself illegal, or invites attention, a ciphertext is a confession. A picture is not.

Where you are hiding from traffic analysis rather than from a reader. The previous chapters established that encryption leaves the pattern of communication visible. Steganography attacks that directly: there is no pattern, because there is no apparent message.

Where the two are combined, which is what a careful party actually does: encrypt the message, then hide the ciphertext. Then an opponent who does not find it learns nothing at all, and an opponent who finds it still faces the cipher. That layering is the right answer to "compare steganography and cryptography": they are not alternatives.

The classical methods

These are the ones MU's reading list names, and they are worth knowing because the ideas recur.

Character marking. Selected letters of printed text are overwritten in pencil. The marks are invisible in ordinary light and show when the paper is held at an angle.

Invisible ink. A substance that leaves no visible trace until heat or a chemical is applied.

Pin punctures. Small pin holes over selected letters, invisible unless the paper is held up to a light.

Typewriter correction ribbon. Used between the lines typed with a black ribbon, so the result is invisible unless the paper is examined under a strong light.

The first-letter method. A message whose first letters, or the letters at some other agreed positions, spell out the real message. The classical name for this is an acrostic, and it is the one form of steganography a student can construct on paper in an examination.

The modern method, performed

A digital image stores each pixel as a number. Changing the last bit of that number changes the brightness of one pixel by one part in 256, which no eye detects. So a message can be written one bit at a time into the last bits of the pixel bytes, and the picture still looks like the picture.

munotes.in81

Steganography: Hiding That There Is a Message at All

The program below builds a 16 by 16 grey picture so that it is complete in itself, hides a twelve-character message in it, prints what changed, and reads the message back.

# A picture, and a message hidden in the last bit of each byte of it.
# The picture is made here rather than read from a file, so the program is
# complete and the numbers below are its own.

WIDTH, HEIGHT = 16, 16

def make_picture():
    """A grey gradient. Each pixel is one byte, 0 black to 255 white."""
    return bytearray(((x * 16 + y * 3) % 256) for y in range(HEIGHT)
                     for x in range(WIDTH))

def to_bits(message):
    data = message.encode("ascii") + b"\x00"
    return [(b >> (7 - i)) & 1 for b in data for i in range(8)]

def hide(picture, message):
    out = bytearray(picture)
    bits = to_bits(message)
    if len(bits) > len(out):
        raise ValueError("the picture is too small for the message")
    for i, bit in enumerate(bits):
        out[i] = (out[i] & 0xFE) | bit
    return out

def reveal(picture):
    bits, chars = [], []
    for byte in picture:
        bits.append(byte & 1)
        if len(bits) == 8:
            value = 0
            for b in bits:
                value = (value << 1) | b
            if value == 0:
                return "".join(chars)
            chars.append(chr(value))
            bits = []
    return "".join(chars)

original = make_picture()
message = "PAPER LEAKED"
carrier = hide(original, message)

print("picture size      :", WIDTH, "by", HEIGHT, "=", len(original), "bytes")
print("message           :", message)
print("bits to hide      :", len(to_bits(message)), "so", len(to_bits(message)),
      "of", len(original), "bytes are touched")
print()
print("the first twelve bytes, before and after:")
print("   before:", " ".join("%3d" % b for b in original[:12]))
print("   after :", " ".join("%3d" % b for b in carrier[:12]))
print("   change:", " ".join("%+3d" % (carrier[i] - original[i]) for i in range(12)))
print()
changed = sum(1 for i in range(len(original)) if original[i] != carrier[i])
print("bytes changed     :", changed, "of", len(original))
print("largest change    :", max(abs(carrier[i] - original[i]) for i in range(len(original))))
print("recovered message :", reveal(carrier))
print()
print("and here is why it is not encryption: anybody who suspects the trick")
print("reads it straight out, with no key at all.")
picture size      : 16 by 16 = 256 bytes
message           : PAPER LEAKED
bits to hide      : 104 so 104 of 256 bytes are touched

the first twelve bytes, before and after:
   before:   0  16  32  48  64  80  96 112 128 144 160 176
   after :   0  17  32  49  64  80  96 112 128 145 160 176
   change:  +0  +1  +0  +1  +0  +0  +0  +0  +0  +1  +0  +0

bytes changed     : 46 of 256
largest change    : 1
recovered message : PAPER LEAKED

and here is why it is not encryption: anybody who suspects the trick
reads it straight out, with no key at all.
munotes.in82

Steganography: Hiding That There Is a Message at All

Read four things out of that run.

104 bits were written and only 46 bytes changed. A bit is only written when it differs from the bit already there, and about half the time it does not. That is worth noticing because it is why the method is hard to see: a random message overwrites a random half of the bits it touches.

The largest change to any byte is 1. Out of a range of 256. The picture is visually identical, and that is the whole claim of least significant bit steganography, stated as a measurement rather than as an assurance.

The message came back exactly. The terminating zero byte tells the reader where to stop, which is a detail every implementation needs and most descriptions omit: without it you cannot tell the message from the picture's own last bits.

A twelve-character message needed 104 of 256 bytes. So the capacity of a cover is about one eighth of its size, and that is the arithmetic to quote: one bit per byte, so one character per eight bytes. A one-megabyte photograph hides about 128 kilobytes, which is a great deal of text, and that is why the method is used.

The weaknesses, honestly

It is not secure, in the sense this subject uses the word. There is no key. Anybody who suspects that the last bits of a picture carry a message reads it out, which is exactly what the reveal function does. The security is entirely in the opponent's ignorance of the method, and the chapter on the symmetric cipher model already dealt with that: it is security through obscurity.

Statistical detection works. The last bits of a real photograph are not random, they are correlated with the image. A hidden message makes them look random, and that difference is measurable. The field that does this is called steganalysis, and simple tests catch simple hides reliably.

The overhead is enormous. Concealing a few hundred bits of message needed a cover of several thousand bits. An answer should quote the ratio: eight cover bits carry one message bit in the method above.

Any change to the cover destroys the message. Recompressing the picture, resizing it, or saving it in a format that discards detail removes the last bits and with them the message. A file that has passed through a social media platform has almost certainly lost it.

Distinctions that carry marks

CryptographySteganography
Hidesthe contents of the messagethe existence of the message
An opponent who interceptsknows a secret is being sentsuspects nothing
Needs a keyyesin the simple form, no
Fails whenthe key is compromisedthe method is guessed
Overheadthe ciphertext is about the size of the plaintextthe cover is many times the message
Survives re-encoding of the carrieryesusually not
Best practiceuse both: encrypt, then hide the ciphertext
munotes.in83

Steganography: Hiding That There Is a Message at All

SteganographyWatermarking
Purposesecret communicationproving ownership or tracing a copy
The hidden data isa message for a recipienta mark about the carrier itself
Should survive attacknot necessarilyyes, that is the requirement
Should be undetectableyesnot always; a visible watermark is normal

A worked example: which one does the job

The requirement. A journalist inside an organisation wants to send a document to a colleague. Two different threats.

Threat A: the network operator can read the traffic. Encryption answers this. The operator sees an encrypted file, cannot read it, and the job is done. Steganography would be pointless extra work.

Threat B: the network operator will act against anybody who sends an encrypted file. Encryption fails here, not because it can be broken but because using it is the offence. Steganography answers this: the journalist sends a photograph.

The correct design for both threats at once. Encrypt the document with a key the colleague already holds, then hide the ciphertext in the photograph. If the cover is never suspected, nothing is learned. If it is suspected and the ciphertext extracted, the cipher still stands. Two independent failures are needed instead of one, which is the principle of defence in depth and the reason the two techniques belong together.

Quick revision

  • Steganography hides the existence of a message; cryptography hides its contents.
  • Classical methods: character marking, invisible ink, pin punctures, typewriter correction ribbon, and the acrostic or first-letter method.
  • Modern method: least significant bit substitution in an image or audio file. One bit per byte, so one character per eight bytes.
  • In the run: a 12-character message in a 256-byte picture touched 104 bytes, changed 46, and the largest change to any byte was 1.
  • A terminator is needed so the reader knows where the message ends.
  • Weaknesses: no key, so it is security through obscurity; statistical steganalysis detects it; huge overhead; destroyed by recompressing the carrier.
  • Watermarking is the related technique for proving ownership, and it must survive attack rather than merely avoid detection.
  • Best practice: encrypt, then hide, so that two independent failures are needed.

Test yourself

1. Distinguish steganography from cryptography. Cryptography conceals the contents of a message, so an interceptor knows that a secret is being sent but cannot read it. Steganography conceals the existence of the message by hiding it inside an innocuous cover, so an interceptor has no reason to suspect a communication at all.

munotes.in84

Steganography: Hiding That There Is a Message at All

2. Name four classical steganographic techniques. Character marking, in which selected letters of printed text are overwritten in pencil; invisible ink; pin punctures over chosen letters; and the use of a typewriter correction ribbon between the lines. The first-letter or acrostic method is a fifth.

3. Explain least significant bit steganography and state its capacity. Each pixel of an image is stored as a number, and changing its lowest bit alters the brightness by one part in 256, which is invisible. The message's bits are written one per byte into those lowest bits. The capacity is therefore one bit per byte of cover, that is one character per eight bytes, so a one-megabyte image holds about 128 kilobytes of text.

4. In the chapter's run, 104 bits were hidden and only 46 bytes changed. Why? Because a bit is only altered when it differs from the bit already in that position, and for a roughly random message that is about half the time. The consequence is that a hide touches fewer bytes than it writes bits, which makes it harder to notice.

5. Give three weaknesses of steganography. It has no key in its simple form, so its security rests entirely on the method not being guessed, which is security through obscurity. Statistical analysis, called steganalysis, detects it, because the lowest bits of a real photograph are correlated with the image and a hidden message makes them look random. And the overhead is very large, with eight cover bits needed per message bit.

6. Why is a hidden message lost when a picture is uploaded to a social media platform? Because such platforms recompress and often resize the image, and both operations discard exactly the fine detail that the lowest bits carry. The cover survives visually and the message does not.

7. What is the correct way to combine the two techniques, and why? Encrypt the message first and hide the resulting ciphertext in the cover. Then an opponent must both discover the hiding method and break the cipher, so two independent defences must fail rather than one; and if the cover is never suspected, nothing at all is learned.

Contents This chapter on its own page

munotes.in85

Chapter Seventeen

Block Ciphers and Stream Ciphers: The Difference That Decides Everything

Syllabus topic Module 1, "Classical Encryption Techniques: Block Cipher Principles"

In one line

A block cipher processes a fixed-size chunk at a time and always produces the same output for the same input and key. A stream cipher processes one bit or byte at a time, combining it with a key stream that never repeats.

In the wording a student can write in an examination: a block cipher processes the input one block of elements at a time, producing an output block for each input block; DES, triple DES and AES are block ciphers. A stream cipher encrypts a digital data stream one bit or one byte at a time; the Vernam cipher, the one-time pad and RC4 are stream ciphers.

Why the distinction decides so much

Because it settles four separate practical questions at once, and every protocol in Module 2 answers them the same way.

Can you start encrypting before the message is complete? A stream cipher can: each byte is enciphered as it arrives. A block cipher must wait for a whole block, and if the last block is short it must be padded, which changes the length of the message and needs a rule.

What happens to the rest of the message if one bit is corrupted in transit? In a stream cipher, one bit of ciphertext gives one wrong bit of plaintext and nothing else. In a block cipher, one wrong bit ruins the whole block, and depending on the mode it may ruin the next block too.

Can the receiver decrypt the tenth block without the first nine? With some block cipher modes, yes. With a stream cipher, only if the key stream can be generated at an offset.

Is the same plaintext going to produce the same ciphertext? With a block cipher in its simplest mode, yes, and that is a serious leak. With a stream cipher it depends entirely on never reusing the key stream, and reusing it is catastrophic, as the one-time pad chapter proved.

The practical answer the world settled on is: use a block cipher, and run it in a mode that turns it into a stream cipher. That is what counter mode and output feedback do, and it is why the chapter on modes of operation exists.

Why you cannot simply build the ideal block cipher

The obvious design for a block cipher is a lookup table: a 4-bit block has 16 possible values, so write down which output each one maps to. That is a general reversible mapping, and it is the strongest possible block cipher of that size, because every reversible mapping is equally likely.

Now count the key. The key has to specify the whole table, so the key is the table.

import math

def digits_of_factorial(n):
    """The number of decimal digits of n!, by Stirling, without computing it."""
    if n < 2:
        return 1
    log10 = (n * math.log10(n / math.e) + 0.5 * math.log10(2 * math.pi * n))
    return int(log10) + 1

for bits in (4, 8, 64, 128):
    entries = 2 ** bits
    print("block of %3d bits" % bits)
    print("   table entries        : %s" % format(entries, ","))
    print("   key = the whole table: %s bits" % format(entries * bits, ","))
    print("   possible mappings    : %s factorial, a number of about %s digits"
          % (format(entries, ","), format(digits_of_factorial(entries), ",")))
munotes.in86

Block Ciphers and Stream Ciphers: The Difference That Decides Everything

block of   4 bits
   table entries        : 16
   key = the whole table: 64 bits
   possible mappings    : 16 factorial, a number of about 14 digits
block of   8 bits
   table entries        : 256
   key = the whole table: 2,048 bits
   possible mappings    : 256 factorial, a number of about 507 digits
block of  64 bits
   table entries        : 18,446,744,073,709,551,616
   key = the whole table: 1,180,591,620,717,411,303,424 bits
   possible mappings    : 18,446,744,073,709,551,616 factorial, a number of about 347,382,171,305,201,303,553 digits
block of 128 bits
   table entries        : 340,282,366,920,938,463,463,374,607,431,768,211,456
   key = the whole table: 43,556,142,965,880,123,323,311,949,751,266,331,066,368 bits
   possible mappings    : 340,282,366,920,938,463,463,374,607,431,768,211,456 factorial, a number of about 12,963,922,773,915,897,742,449,490,839,078,382,338,049 digits

Read the 64-bit row. The ideal 64-bit block cipher needs a key of 1,180,591,620,717,411,303,424 bits, which is about 147 million terabytes. It is not a cipher anybody can use; it is a definition of what a cipher should behave like. The mapping count in the last column is printed by Stirling's formula rather than computed, because the number itself has more digits than could be stored: about 347 followed by eighteen further digits of them.

So a real block cipher has a short key, say 56 or 128 bits, and is built to approximate the ideal mapping. It selects one mapping out of the tiny number its key can address, and the design problem is to make that small family look, to an attacker, like a random choice from the enormous one. That problem is what the next chapter's structure solves.

Two more things follow, and both are worth stating in an answer.

A block cipher's block size matters separately from its key size. A 64-bit block is small enough that after about 2 to the power 32 blocks a repeat becomes likely, whatever the key length, and that is the weakness behind the retirement of 3DES rather than any weakness in its key. AES uses 128-bit blocks for this reason.

A short key is not a compromise on security, only on generality. A 128-bit key gives 2 to the power 128 mappings out of an unimaginably larger set, and 2 to the power 128 is already beyond brute force.

munotes.in87

Block Ciphers and Stream Ciphers: The Difference That Decides Everything

Choosing between them

QuestionBlock cipherStream cipher
Unit processeda fixed block, 64 or 128 bitsone bit or byte
Padding neededyes, unless the length is a multiple of the blockno
Can start before the message is completenoyes
Effect of one corrupted ciphertext bitthe whole block, and sometimes the nextone plaintext bit
Random access to the middle of the messagein some modesonly if the key stream can be offset
Same plaintext, same ciphertextin the simplest mode, yesnever, if the key stream is never reused
Speed in softwarefastusually faster
Hardware costhigherlower
Suitsfiles, records, anything storeda live connection, a voice call, a low-power radio link
Examples on this syllabusDES, 3DES, AESthe one-time pad, Vernam, RC4
Danger to avoidusing the simplest modereusing the key stream

The last row is the one to carry away. Each family has exactly one catastrophic mistake, and they are different mistakes: for a block cipher it is electronic codebook mode, and for a stream cipher it is key reuse. Both have their own chapter later.

A worked example: which one for which job

Job 1: encrypt a student's answer script, stored as a PDF, in a college archive.

A block cipher, in cipher block chaining or counter mode. The file is complete before encryption starts, so nothing is gained from a stream cipher; the file will be stored for years, so a 128-bit block cipher is the safe choice; and if one sector of the disk goes bad it is better to lose one block than to lose the alignment of everything after it.

Job 2: encrypt a voice call over a poor mobile connection.

A stream cipher, or a block cipher in counter mode behaving as one. Each sample must be enciphered and sent immediately, so waiting for a block is not acceptable; and a corrupted bit must corrupt one sample and not a whole block, because a click is tolerable and a burst of noise is not.

Job 3: encrypt the traffic of a college portal over the web.

A block cipher in an authenticated mode, because that is what TLS specifies and because the data is a mixture of short and long messages. Notice that the answer is not decided by the block-against-stream question alone: it is decided by the protocol, which is the subject of Module 2.

The step that carries the marks. In each case the reason names a property from the table: completeness of the message, the effect of a corrupted bit, and the lifetime of the data. An answer that says "a block cipher is more secure" has not answered, because neither family is more secure than the other.

munotes.in88

Block Ciphers and Stream Ciphers: The Difference That Decides Everything

What beginners get wrong here

"A stream cipher is weaker." It is not. The one-time pad is a stream cipher and is the only unconditionally secure cipher there is. What is true is that stream ciphers fail catastrophically when misused, and that the commonest misuse, key reuse, is easy to commit.

Confusing the block size with the key size. DES has a 64-bit block and a 56-bit key. AES has a 128-bit block and a key of 128, 192 or 256 bits. They are independent choices and both matter.

Thinking a block cipher is inherently deterministic. The cipher is, and that is why modes exist. Cipher block chaining with a fresh initialisation vector, or counter mode with a fresh counter, gives a different ciphertext every time for the same plaintext.

Believing a bigger block is always better. A larger block means more padding overhead on short messages and more state in hardware. 128 bits is the current answer because it is large enough that repeats are not a concern.

Quick revision

  • Block cipher: a fixed-size block at a time, deterministic for a given key, needs padding. DES, 3DES, AES.
  • Stream cipher: one bit or byte at a time, combined with a key stream. One-time pad, Vernam, RC4.
  • The ideal block cipher is an arbitrary reversible mapping, and its key is the whole table: for a 64-bit block that is about 1.18 times 10 to the power 21 bits. Unusable, so real ciphers approximate it with a short key.
  • Block size and key size are independent. DES: 64-bit block, 56-bit key. AES: 128-bit block, 128, 192 or 256-bit key.
  • A small block is a weakness in itself: repeats become likely after about 2 to the power 32 blocks for a 64-bit block.
  • One corrupted ciphertext bit: a whole block for a block cipher, one bit for a stream cipher.
  • Each family has one catastrophic mistake: electronic codebook mode for block ciphers, key stream reuse for stream ciphers.
  • The practical answer: a block cipher in a mode that behaves like a stream cipher.

Test yourself

1. Define a block cipher and a stream cipher, and give two examples of each from this syllabus. A block cipher processes the input one fixed-size block at a time and produces one output block for each input block; DES and AES are examples. A stream cipher encrypts a data stream one bit or byte at a time by combining it with a key stream; the one-time pad and RC4 are examples.

2. What is the ideal block cipher, and why can it not be used? It is an arbitrary reversible mapping from every possible input block to a distinct output block, which is the strongest block cipher of that size. It cannot be used because the key must specify the whole mapping: for a 64-bit block that is over 18 million million million entries and a key of about 1.18 times 10 to the power 21 bits. A practical cipher therefore uses a short key and approximates the ideal.

munotes.in89

Block Ciphers and Stream Ciphers: The Difference That Decides Everything

3. Distinguish block size from key size, with DES and AES as examples. The block size is how much plaintext is transformed at once; the key size is how many bits of secret select the transformation. DES has a 64-bit block and a 56-bit key; AES has a 128-bit block and a key of 128, 192 or 256 bits. A cipher can be weak in one and strong in the other.

4. Why is a 64-bit block a weakness independent of the key length? Because the number of distinct blocks is limited, so in a long enough message a repeated ciphertext block becomes likely after about 2 to the power 32 blocks, and a repeat leaks information about the plaintext whatever the key. This is the reason a 64-bit block cipher is unsuitable for large volumes of data even with a long key.

5. A corrupted bit arrives in the ciphertext. Compare the damage under a block cipher and a stream cipher. Under a stream cipher only the corresponding plaintext bit is wrong, because each bit is combined with its own key stream bit independently. Under a block cipher the entire block containing that bit decrypts to rubbish, and in a chaining mode the following block is also affected.

6. Which family would you choose for a live voice call, and why? A stream cipher, or a block cipher in counter mode behaving as one. Each sample must be enciphered and transmitted immediately rather than waiting for a full block, and a corrupted bit should damage one sample only rather than a whole block, since a single click is tolerable where a burst of noise is not.

7. Name the one catastrophic mistake for each family. For a block cipher, using electronic codebook mode, because identical plaintext blocks produce identical ciphertext blocks and the structure of the data shows through. For a stream cipher, reusing the key stream, because subtracting two ciphertexts encrypted under the same stream removes the key entirely and leaves the difference of the plaintexts.

Contents This chapter on its own page

munotes.in90

Chapter Eighteen

The Feistel Cipher Structure, Confusion and Diffusion

Syllabus topic Module 1, "Classical Encryption Techniques: Block Cipher Principles"

In one line

Split the block in half. Each round, apply a function to the right half using a subkey, exclusive-or the result into the left half, then swap the halves. Because the swap and the exclusive-or are both reversible, the whole cipher is reversible even when the function is not, so the function can be as complicated as you like.

In the wording a student can write in an examination: the Feistel cipher, proposed by Horst Feistel of IBM in 1973, is a practical realisation of Shannon's product cipher. The plaintext block of 2w bits is divided into two halves L and R. Each of n rounds takes the left and right halves from the previous round and produces

L(i) = R(i - 1)

R(i) = L(i - 1) XOR F(R(i - 1), K(i))

where F is the round function and K(i) the subkey for round i. After the last round the two halves are swapped to produce the ciphertext. Decryption uses exactly the same algorithm with the subkeys applied in reverse order.

Why the structure exists

Two problems from the previous chapter, both solved by one idea.

Problem 1: the ideal block cipher needs an impossible key. Shannon's answer was the product cipher: alternate substitutions and permutations, many times. Each stage is weak, and the composition is strong. Feistel's structure is a way of executing that idea in hardware with a short key, and it does it by deriving a different subkey for each round from one short master key.

Problem 2: a strong round function is hard to invert. If you want the round function to be a complicated mess of substitutions, table lookups and shifts, you then have to build its inverse for decryption, and every complication doubles the work. Feistel's structure removes the requirement entirely. The round function need not be invertible, need not be one to one, and need not even preserve the number of bits it is given. That freedom is what lets DES use eight S-boxes that each throw two bits away.

Shannon's two goals

Every cipher design in this book is judged against these two, so they are defined once here.

Confusion makes the relationship between the key and the ciphertext as complicated as possible. If an attacker changes a guess at one key bit, the effect on the ciphertext should be complicated and unpredictable. Confusion is provided by substitution, and in DES it is provided by the S-boxes.

Diffusion spreads the statistical structure of the plaintext over the whole ciphertext, so that each plaintext bit affects many ciphertext bits and each ciphertext bit depends on many plaintext bits. The purpose is to dissipate the redundancy of the plaintext, so that letter frequencies and word patterns have nowhere to show. Diffusion is provided by permutation, and in DES by the expansion, the permutation P, and the swapping of halves.

munotes.in91

The Feistel Cipher Structure, Confusion and Diffusion

The rule to remember: confusion is about the KEY, diffusion is about the PLAINTEXT. Students reverse them, and it is the single commonest slip in this topic.

Neither alone is enough. A cipher with confusion and no diffusion leaves the plaintext's patterns intact within each small unit, which is a monoalphabetic cipher. A cipher with diffusion and no confusion moves the patterns around without disguising them, which is a transposition. Alternating the two over many rounds is a product cipher, and it is what every block cipher on this syllabus does.

The proof, performed

The listing below builds a four-round Feistel cipher on a 16-bit block, with a round function that is deliberately not invertible: it throws away the high four bits of its input. Then it encrypts, decrypts, and searches for two inputs to the round function that give the same output.

def f(half, subkey):
    """Not invertible on purpose: the high four bits are thrown away."""
    low = half & 0b00001111
    return (((low * 13) + 7) ^ subkey) & 0xFF

def rounds(left, right, subkeys):
    trace = [(0, left, right)]
    for i, k in enumerate(subkeys, 1):
        left, right = right, left ^ f(right, k)
        trace.append((i, left, right))
    return left, right, trace

def encrypt(block, subkeys):
    left, right = block >> 8, block & 0xFF
    l, r, trace = rounds(left, right, subkeys)
    return ((r << 8) | l), trace

def decrypt(block, subkeys):
    left, right = block >> 8, block & 0xFF
    l, r, trace = rounds(left, right, subkeys[::-1])
    return ((r << 8) | l), trace

SUBKEYS = [0x5A, 0x3C, 0xF0, 0x0F]
plain = 0x1234

cipher, etrace = encrypt(plain, SUBKEYS)
print("plaintext  = %04X" % plain)
print("subkeys    =", " ".join("%02X" % k for k in SUBKEYS))
print()
print("encryption, round by round:")
for i, l, r in etrace:
    print("   after round %d:  L = %02X  R = %02X" % (i, l, r))
print("   swap the halves: ciphertext = %04X" % cipher)
print()
back, dtrace = decrypt(cipher, SUBKEYS)
print("decryption, the SAME rounds with the subkeys reversed:")
for i, l, r in dtrace:
    print("   after round %d:  L = %02X  R = %02X" % (i, l, r))
print("   swap the halves: plaintext = %04X" % back)
print("recovered the plaintext:", back == plain)
print()
print("the round function is NOT invertible. Two different inputs, one output:")
seen = {}
for x in range(256):
    y = f(x, 0x5A)
    if y in seen:
        print("   f(%02X) = f(%02X) = %02X" % (seen[y], x, y))
        break
    seen[y] = x
print("   so a Feistel cipher does not need an invertible round function,")
print("   which is the whole reason the structure exists.")
munotes.in92

The Feistel Cipher Structure, Confusion and Diffusion

plaintext  = 1234
subkeys    = 5A 3C F0 0F

encryption, round by round:
   after round 0:  L = 12  R = 34
   after round 1:  L = 34  R = 73
   after round 2:  L = 73  R = 26
   after round 3:  L = 26  R = D6
   after round 4:  L = D6  R = 7C
   swap the halves: ciphertext = 7CD6

decryption, the SAME rounds with the subkeys reversed:
   after round 0:  L = 7C  R = D6
   after round 1:  L = D6  R = 26
   after round 2:  L = 26  R = 73
   after round 3:  L = 73  R = 34
   after round 4:  L = 34  R = 12
   swap the halves: plaintext = 1234
recovered the plaintext: True

the round function is NOT invertible. Two different inputs, one output:
   f(00) = f(10) = 5D
   so a Feistel cipher does not need an invertible round function,
   which is the whole reason the structure exists.

Now read the two traces against each other, because that is the proof.

Encryption produced, in order, the pairs (12, 34), (34, 73), (73, 26), (26, D6), (D6, 7C). Decryption produced (7C, D6), (D6, 26), (26, 73), (73, 34), (34, 12). The second list is the first list read from the bottom up, with each pair swapped. The same rounds, in the same direction, with the subkeys reversed, walked the cipher backwards. Nothing was inverted.

And the round function is provably not invertible: f(00) and f(10) are both 5D, so given 5D you cannot say which input produced it. Yet the cipher decrypts exactly. That is the Feistel property, and the reason for it is worth stating as one sentence: the round function's output is never something you have to undo, it is only something you exclusive-or in, and an exclusive-or is undone by repeating it.

Why it works, in symbols

At round i, encryption computes L(i) = R(i-1) and R(i) = L(i-1) XOR F(R(i-1), K(i)).

To go backwards you need L(i-1) and R(i-1) from L(i) and R(i). Take them in the easy order:

R(i-1) = L(i), directly, because that is what the first equation says.

L(i-1) = R(i) XOR F(R(i-1), K(i)), and you now know R(i-1), so you can compute F again and exclusive-or it out. You never needed to invert F; you only needed to be able to run it forwards on a value you already had.

That is the whole argument, and it is worth five marks on its own.

The parameters of a Feistel cipher

A question asking you to "discuss the design features of a Feistel cipher" wants this list, each with the trade-off.

munotes.in93

The Feistel Cipher Structure, Confusion and Diffusion

Block size. Larger means greater diffusion and greater security, and slower. 64 bits was traditional; 128 bits is the modern choice.

Key size. Larger means greater resistance to brute force, and slower. 64 bits was once thought adequate, 56 bits was DES's and is now inadequate, and 128 bits is the modern minimum.

Number of rounds. A single round gives very little; multiple rounds give increasing security. The usual figure is 16. The right number is the one after which no known attack does better than brute force.

Subkey generation algorithm. Greater complexity makes cryptanalysis harder. It must produce a different subkey per round from one master key.

Round function F. Greater complexity makes cryptanalysis harder. This is where confusion lives, and it is the part that is designed rather than chosen.

Two further considerations, which are engineering rather than security.

Fast software encryption and decryption. A cipher is often embedded in an application rather than a chip, so the speed of a software implementation matters.

Ease of analysis. A cipher whose design is easy to explain is easier to argue about, and therefore easier to trust. DES's designers did not publish their reasoning, which is exactly why DES was distrusted for years.

Distinctions that carry marks

ConfusionDiffusion
Relatesthe key to the ciphertextthe plaintext to the ciphertext
Purposemake the key's effect complicateddissipate the plaintext's redundancy
Provided bysubstitutionpermutation
In DESthe S-boxesthe expansion, the permutation P, the swap
Due toShannon, 1949Shannon, 1949
Feistel structureSubstitution-permutation network
Operates onhalf the block each roundthe whole block each round
Round function must be invertiblenoyes
Encryption and decryptionthe same code, subkeys reverseddifferent code
Rounds neededmore, because half the block moves per roundfewer
ExampleDES, 3DESAES

That second table is the most useful comparison in this part of the syllabus, because the next two chapters are about DES and the one after is about AES, and their difference is exactly this.

What beginners get wrong here

Swapping confusion and diffusion. Confusion is about the key; diffusion is about the plaintext. Write it down.

Saying the round function must be reversible. It must not be, in the sense that nothing requires it, and DES's S-boxes are not. This is the point of the structure and the thing to say.

Forgetting the final swap. After the last round the halves are swapped before the ciphertext is assembled. Without it, decryption with reversed subkeys does not work, and it is the detail that breaks a hand-worked answer.

Thinking more rounds always means more security. Each round costs time, and past a certain point the gain is nothing because brute force is already the best attack. Sixteen is DES's answer, not a law.

munotes.in94

The Feistel Cipher Structure, Confusion and Diffusion

Calling AES a Feistel cipher. It is not. It is a substitution-permutation network, it operates on the whole block each round, and its stages are individually invertible.

Quick revision

  • Feistel: L(i) = R(i-1), R(i) = L(i-1) XOR F(R(i-1), K(i)), halves swapped after the last round.
  • Decryption is the same algorithm with the subkeys in reverse order.
  • The round function need not be invertible: proved in the run, where f(00) and f(10) both gave 5D and the cipher still decrypted.
  • Why: R(i-1) = L(i) directly, and then F is recomputed forwards and exclusive-ored out.
  • Confusion relates the key to the ciphertext, by substitution. Diffusion relates the plaintext to the ciphertext, by permutation. Shannon, 1949.
  • Parameters: block size, key size, number of rounds, subkey algorithm, round function; plus fast software operation and ease of analysis.
  • A Feistel cipher works on half the block per round; a substitution-permutation network on the whole block, and its stages must be invertible.
  • DES and 3DES are Feistel. AES is not.

Test yourself

1. Write the Feistel round equations and say what happens after the last round. L(i) = R(i - 1) and R(i) = L(i - 1) XOR F(R(i - 1), K(i)). After the last round the two halves are swapped before the ciphertext is assembled.

2. How is a Feistel cipher decrypted? By running exactly the same algorithm on the ciphertext with the subkeys applied in reverse order, so K(n) first and K(1) last, with the same final swap. No separate decryption algorithm is needed.

3. Why does the round function not need to be invertible? Give the argument. Because the round output is combined by exclusive-or rather than substituted in. Going backwards, R(i - 1) is available immediately since it equals L(i); the function can then be recomputed forwards on that value and exclusive-ored out of R(i) to recover L(i - 1). The function is therefore only ever evaluated in the forward direction. The chapter's run confirms it: a round function for which f(00) and f(10) both give 5D still yields a cipher that decrypts exactly.

4. Define confusion and diffusion and say which operation provides each. Confusion makes the relationship between the key and the ciphertext as complex as possible and is provided by substitution. Diffusion spreads the statistical structure of the plaintext across the whole ciphertext, dissipating its redundancy, and is provided by permutation.

5. List the design parameters of a Feistel cipher. Block size, key size, number of rounds, subkey generation algorithm and the round function, with the two further engineering considerations of fast software encryption and decryption, and ease of analysis.

munotes.in95

The Feistel Cipher Structure, Confusion and Diffusion

6. Why is a cipher with confusion but no diffusion weak? Give an example. Because the plaintext's statistical structure survives inside each unit that is substituted, so frequency analysis applies. A monoalphabetic substitution cipher is exactly that: the key's effect on each letter is arbitrary, but each letter keeps its own frequency and the cipher falls to counting.

7. Is AES a Feistel cipher? Justify your answer. No. AES is a substitution-permutation network: each round transforms the whole block rather than half of it, and each of its stages is individually invertible, so decryption requires the inverse of each stage rather than the same code with reversed subkeys. A Feistel cipher transforms half the block per round and needs no inverse of its round function.

Contents This chapter on its own page

munotes.in96

Chapter Nineteen

The Data Encryption Standard: The Shape of It

Syllabus topic Module 1, "Classical Encryption Techniques: The Data Encryption Standard"

In one line

A 64-bit block, a 56-bit key, sixteen Feistel rounds between two fixed permutations. That is the whole shape of DES, and every other detail hangs off it.

In the wording a student can write in an examination: the Data Encryption Standard is a symmetric block cipher adopted as a Federal Information Processing Standard in 1977. It encrypts a 64-bit plaintext block under a 56-bit key, producing a 64-bit ciphertext block. The algorithm has three parts: an initial permutation of the input block; sixteen rounds of a Feistel structure, each using a 48-bit subkey derived from the key; and a final permutation, which is the inverse of the initial one. A 32-bit swap between the last round and the final permutation makes decryption the same algorithm with the subkeys reversed.

The history, and the date it ended

DES matters historically as much as technically, and the dates are examinable.

1973. The National Bureau of Standards issues a public request for a standard encryption algorithm. Nothing suitable is submitted.

1974. A second request. IBM submits a cipher developed from its earlier Lucifer design, by a team including Horst Feistel.

1977. The algorithm is adopted as FIPS PUB 46. Two things were changed from IBM's submission and both were controversial for twenty years: the key was reduced to 56 bits, and the design criteria for the S-boxes were classified.

1999. FIPS 46-3 reaffirms the standard but permits DES only for legacy systems and designates Triple DES as the preferred algorithm.

2001. FIPS 197 publishes AES as the replacement.

19 May 2005. DES is withdrawn. Its own cover sheet says so: the publication "was withdrawn on May 19, 2005 and is provided here only for historical purposes."

So a student in 2026 is studying a cipher that has been formally dead for over twenty years, and the honest reason to study it is that it is the clearest worked example of a Feistel cipher in existence, that every later cipher is described by comparison with it, and that MU sets it. It is not an option for protecting anything.

The three parts

Part 1: the initial permutation, IP. The 64 input bits are rearranged according to a fixed table. The table is public, has no key in it, and adds no cryptographic strength whatever. It exists because DES was designed for hardware, where the bits arrive in bytes and the permutation is free.

Part 2: sixteen rounds. The permuted block is split into a left half L0 and a right half R0, each of 32 bits. Then for each round i from 1 to 16, using the Feistel equations from the previous chapter, L(i) = R(i-1) and R(i) = L(i-1) XOR F(R(i-1), K(i)), where K(i) is that round's 48-bit subkey and F is the round function of the next chapter.

munotes.in97

The Data Encryption Standard: The Shape of It

Part 3: the 32-bit swap and the final permutation. After round 16 the two halves are swapped, giving R16 L16 rather than L16 R16, and the result is passed through FP, the inverse of IP. The swap is what makes decryption work with the same algorithm, and it is the step most often left out of a drawn answer.

The permutations, and the proof that they are inverses

# The two outer permutations of DES, from Tables 1 and 2 of FIPS 46-3.
# They are inverses of each other, which is proved below rather than asserted.

IP = [58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4,
      62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8,
      57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
      61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7]

FP = [40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31,
      38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29,
      36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27,
      34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25]

def bits(data):
    return [(b >> (7 - i)) & 1 for b in data for i in range(8)]

def unbits(bl):
    out = bytearray()
    for i in range(0, len(bl), 8):
        v = 0
        for b in bl[i:i + 8]:
            v = (v << 1) | b
        out.append(v)
    return bytes(out)

def permute(bl, table):
    return [bl[i - 1] for i in table]

block = bytes.fromhex("0123456789ABCDEF")
b = bits(block)
print("the block              :", block.hex().upper())
print("as 64 bits             :", "".join(str(x) for x in b))
print()

after_ip = permute(b, IP)
print("after the initial permutation:")
print("   bits                :", "".join(str(x) for x in after_ip))
print("   as bytes            :", unbits(after_ip).hex().upper())
print("   L0                  :", unbits(after_ip[:32]).hex().upper())
print("   R0                  :", unbits(after_ip[32:]).hex().upper())
print()

print("IP takes bit 58 of the input to position 1. Check it:")
print("   input bit 58        :", b[57])
print("   output bit 1        :", after_ip[0])
print()

restored = permute(after_ip, FP)
print("the final permutation undoes the initial one:")
print("   FP(IP(block))       :", unbits(restored).hex().upper())
print("   equals the block    :", unbits(restored) == block)
print()

print("and it holds for every one of the 64 single-bit blocks:")
bad = 0
for i in range(64):
    one = [1 if j == i else 0 for j in range(64)]
    if permute(permute(one, IP), FP) != one:
        bad += 1
print("   blocks that failed  :", bad, "of 64")
munotes.in98

The Data Encryption Standard: The Shape of It

the block              : 0123456789ABCDEF
as 64 bits             : 0000000100100011010001010110011110001001101010111100110111101111

after the initial permutation:
   bits                : 1100110000000000110011001111111111110000101010101111000010101010
   as bytes            : CC00CCFFF0AAF0AA
   L0                  : CC00CCFF
   R0                  : F0AAF0AA

IP takes bit 58 of the input to position 1. Check it:
   input bit 58        : 1
   output bit 1        : 1

the final permutation undoes the initial one:
   FP(IP(block))       : 0123456789ABCDEF
   equals the block    : True

and it holds for every one of the 64 single-bit blocks:
   blocks that failed  : 0 of 64

Read four things out of the run.

How to read a permutation table. The first entry of IP is 58, which means "the first output bit is the fifty-eighth input bit". The program checked exactly that: input bit 58 is 1 and output bit 1 is 1. Students read these tables the other way round, as "bit 1 goes to position 58", and every hand-worked answer then comes out wrong. The table is read as a list of sources, not of destinations.

L0 and R0 are CC00CCFF and F0AAF0AA. Any published walk-through of DES on this block gives the same two values, so this chapter can be checked against an independent source. That is worth doing at least once with a cipher this fiddly.

FP really is the inverse of IP, and the check is not one block but all 64 single-bit blocks, which between them pin down the whole permutation. A permutation is determined by where it sends each single bit, so 0 failures of 64 is a complete proof and not a sample.

Neither permutation uses the key. Both tables are printed in the standard, so an attacker applies IP and FP as freely as you do. They add nothing to security, and an answer should say so: they are there because DES was built for hardware in 1977.

A worked example: encrypting one block, in outline

The setting. Plaintext 0123456789ABCDEF, key 133457799BBCDFF1. This pair is the standard illustration, so every intermediate value can be checked against any other source.

Step 1: the key schedule. From the 64-bit key, sixteen 48-bit subkeys K1 to K16 are produced. The next chapter but one does this in full; the first subkey comes out 1B02EFFC7072.

Step 2: the initial permutation. Done in the run above, giving L0 = CC00CCFF and R0 = F0AAF0AA.

Step 3: round 1. L1 takes the value of R0, which is F0AAF0AA. And R1 is L0 exclusive-ored with F(R0, K1). The next chapter computes that round function and it comes out 234AA9BB, so R1 is CC00CCFF exclusive-ored with 234AA9BB, which is EF4A6544.

Step 4: rounds 2 to 16. The same step fifteen more times, with K2 to K16.

munotes.in99

The Data Encryption Standard: The Shape of It

Step 5: swap and permute. After round 16 the halves are swapped and FP applied, giving the ciphertext 85E813540F0AB405.

The step that carries the marks. Step 3 is the one an examination asks you to perform. Everything else is bookkeeping, and the bookkeeping is where a hand-worked answer goes wrong: the halves must be tracked in the right order, and the swap at the end must not be forgotten.

The parameters, as a table

DES
Block size64 bits
Key size56 bits of key material, in a 64-bit key with 8 parity bits
StructureFeistel
Rounds16
Subkey size48 bits
Round function input32 bits and a 48-bit subkey
Outer permutationsIP and its inverse FP, no key, no strength
Keyspace2 to the power 56
StandardFIPS PUB 46 (1977), 46-1, 46-2, 46-3 (1999)
Withdrawn19 May 2005

What beginners get wrong here

Reading the permutation tables backwards. Entry i of the table names the source bit for output position i.

Calling the key 64 bits. The key is presented as 64 bits, but every eighth bit is a parity bit, so the key material is 56 bits and the keyspace is 2 to the power 56. Saying 64 bits inflates the keyspace by a factor of 256 and is the commonest error about DES.

Forgetting the 32-bit swap. After round 16 the halves are swapped before FP. It is what makes the same algorithm decrypt.

Thinking IP adds security. It does not. It is published and keyless.

Saying DES is secure, or insecure "because it is old". It is insecure because its key is 56 bits, and it was withdrawn on 19 May 2005. Give the reason and the date.

Quick revision

  • DES: 64-bit block, 56-bit key (in a 64-bit key with 8 parity bits), 16 Feistel rounds, 48-bit subkeys.
  • Three parts: initial permutation IP, sixteen rounds, 32-bit swap, then final permutation FP, the inverse of IP.
  • Both permutations are keyless and published and add no strength; they exist for 1977 hardware.
  • A permutation table is read as a list of sources: entry 1 of IP is 58, so output bit 1 is input bit 58.
  • On block 0123456789ABCDEF, IP gives L0 = CC00CCFF and R0 = F0AAF0AA.
  • FP is the inverse of IP, checked over all 64 single-bit blocks with 0 failures.
  • History: request 1973, IBM's Lucifer-derived submission 1974, adopted 1977 as FIPS 46; key cut to 56 bits and S-box criteria classified; FIPS 46-3 in 1999 made 3DES preferred; withdrawn 19 May 2005.
  • With key 133457799BBCDFF1 the ciphertext of that block is 85E813540F0AB405.
munotes.in100

The Data Encryption Standard: The Shape of It

Test yourself

1. Give the block size, key size and number of rounds of DES, and its structure. A 64-bit block, a 56-bit key presented as 64 bits with every eighth bit a parity bit, and sixteen rounds of a Feistel structure with 48-bit subkeys.

2. Describe the three parts of DES encryption. First an initial permutation IP rearranges the 64 input bits. Then sixteen Feistel rounds, each taking the left and right 32-bit halves and computing L(i) = R(i - 1) and R(i) = L(i - 1) XOR F(R(i - 1), K(i)). Finally the two halves are swapped and the inverse permutation FP is applied to give the ciphertext.

3. What do IP and FP contribute to the security of DES? Nothing. Both are fixed, published permutations with no key, so an attacker can apply and undo them at will. They were included because DES was designed for hardware implementation, in which a bit permutation costs nothing.

4. How is the entry 58 at the start of the IP table read? As "output bit 1 is input bit 58". A permutation table lists, for each output position, the input position it draws from, not the destination of each input bit.

5. Why is there a 32-bit swap after the sixteenth round? So that decryption is the same algorithm with the subkeys in reverse order. Without the swap the output of the last round would be in the wrong order for the reversed schedule to unwind it, and a separate decryption algorithm would be needed.

6. Why is it wrong to say DES has a 64-bit key? Because eight of those 64 bits are parity bits, one per byte, used only to detect errors in the key. The effective key material is 56 bits, so the keyspace is 2 to the power 56 rather than 2 to the power 64, which is 256 times smaller.

7. When was DES withdrawn, and what replaced it? It was withdrawn on 19 May 2005. FIPS 46-3 of 1999 had already restricted it to legacy use and designated Triple DES as preferred, and FIPS 197 of 2001 published AES as the replacement.

Contents This chapter on its own page

munotes.in101

Chapter Twenty

Inside a DES Round: Expansion, the S-boxes and the Permutation

Syllabus topic Module 1, "Classical Encryption Techniques: The Data Encryption Standard"

In one line

Stretch 32 bits to 48, add the subkey, squeeze back to 32 through eight lookup tables, then shuffle. That is the function F, and the eight tables are where DES's strength lives.

In the wording a student can write in an examination: the DES round function F takes a 32-bit input R and a 48-bit subkey K and produces a 32-bit output, in four steps. The expansion permutation E expands the 32 bits to 48 by duplicating sixteen of them. The result is exclusive-ored with the 48-bit subkey. The 48 bits are divided into eight groups of six, and each group is replaced by four bits through its own substitution box, or S-box, giving 32 bits in all. Those 32 bits are finally rearranged by the permutation P.

Why F is built this way

Each of the four steps does a job, and an examination answer is expected to name the job rather than only the step.

The expansion permutation exists so the whole subkey can be used. The subkey is 48 bits and R is 32, so they cannot be exclusive-ored directly. Expanding R to 48 bits solves that, and expanding it by duplicating bits rather than padding does something better: it means one input bit affects two S-boxes, which is diffusion. The bits duplicated are the ones at the edges of each four-bit group, which is why the E table's rows overlap.

The exclusive-or is where the key enters. It is the only place in the whole round where the key is used, and it is deliberately simple. All the complexity is in what comes next.

The S-boxes are where confusion lives. They are the only non-linear part of DES. Everything else, the permutations and the exclusive-or, is linear, and a cipher made only of linear operations can be solved as a system of equations. Remove the S-boxes and DES falls to linear algebra in a moment. They are also the part whose design criteria were classified in 1977, which is why DES was distrusted for twenty years; when differential cryptanalysis was published in 1990 the S-boxes turned out to be unusually well chosen against it, which suggested the designers had known about it all along.

The permutation P spreads the S-box outputs. Without it, the four bits coming out of S-box 1 would go back into the neighbourhood they came from, and the diffusion would be local. P sends each S-box's four output bits to four different S-boxes in the next round, so that after a few rounds every bit depends on every other.

How an S-box is read

This is the mechanical skill the topic tests, and it has one trick in it.

munotes.in102

Inside a DES Round: Expansion, the S-boxes and the Permutation

Each S-box is a table of 4 rows and 16 columns holding numbers from 0 to 15. It takes six bits and gives four.

  • The outer two bits, that is the first and the sixth, give the row, read as a two-bit number from 0 to 3.
  • The middle four bits, that is bits two to five, give the column, read as a four-bit number from 0 to 15.
  • The entry at that row and column is the output, written as four bits.

The trick is that the row comes from the first and last bits and not from the first two. Getting that wrong is the commonest error in the whole of DES, and the run below prints the two parts separately for every box so that the reader can see which bits went where.

The round function, computed on the standard values

# The DES round function F, from Tables 3, 4 and 5 of FIPS 46-3: the expansion
# permutation E, the eight S-boxes, and the permutation P.

E = [32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13,
     12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21,
     22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1]

P = [16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10,
     2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25]

S = [
 [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7],
  [0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8],
  [4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0],
  [15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]],
 [[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10],
  [3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5],
  [0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15],
  [13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]],
 [[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8],
  [13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1],
  [13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7],
  [1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]],
 [[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15],
  [13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9],
  [10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4],
  [3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]],
 [[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9],
  [14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6],
  [4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14],
  [11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]],
 [[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11],
  [10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8],
  [9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6],
  [4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]],
 [[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1],
  [13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6],
  [1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2],
  [6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]],
 [[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7],
  [1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2],
  [7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8],
  [2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]],
]

def hex_bits(hexstr):
    n = len(hexstr) * 4
    v = int(hexstr, 16)
    return [(v >> (n - 1 - i)) & 1 for i in range(n)]

def bin_bits(binstr):
    return [int(c) for c in binstr]

def to_hex(bl):
    v = 0
    for b in bl:
        v = (v << 1) | b
    return "%0*X" % (len(bl) // 4, v)

def permute(bl, table):
    return [bl[i - 1] for i in table]

# R0 and K1 from the standard walk-through of key 133457799BBCDFF1
R = hex_bits("F0AAF0AA")
K = bin_bits("000110110000001011101111111111000111000001110010")

print("R (32 bits)       :", to_hex(R))
print("subkey K (48 bits):", to_hex(K))
print()

expanded = permute(R, E)
print("after the expansion permutation E, 32 bits become 48:")
print("   E(R)           :", to_hex(expanded))
x = [a ^ b for a, b in zip(expanded, K)]
print("   E(R) XOR K     :", to_hex(x))
print()

print("the 48 bits in eight groups of six, one per S-box:")
print("   " + " ".join("".join(str(b) for b in x[i * 6:i * 6 + 6]) for i in range(8)))
print()

out = []
print("each group of six gives four bits:")
for i in range(8):
    g = x[i * 6:i * 6 + 6]
    row = g[0] * 2 + g[5]
    col = g[1] * 8 + g[2] * 4 + g[3] * 2 + g[4]
    v = S[i][row][col]
    out += [(v >> 3) & 1, (v >> 2) & 1, (v >> 1) & 1, v & 1]
    print("   S%d: bits %s  outer %d%d = row %d, inner %s = column %2d, entry %2d = %s"
          % (i + 1, "".join(str(b) for b in g), g[0], g[5], row,
             "".join(str(b) for b in g[1:5]), col, v,
             "".join(str(b) for b in [(v >> 3) & 1, (v >> 2) & 1, (v >> 1) & 1, v & 1])))
print()
print("the eight S-box outputs, joined:", to_hex(out))
print("after the permutation P        :", to_hex(permute(out, P)))
print()
print("every S-box takes 6 bits to 4, so it is not invertible:")
first = S[0]
print("   S1 row 0 column 0 gives", first[0][0], "and row 1 column 1 gives", first[1][1])
print("   48 bits in, 32 bits out: F throws away one third of what it is given.")
munotes.in103

Inside a DES Round: Expansion, the S-boxes and the Permutation

R (32 bits)       : F0AAF0AA
subkey K (48 bits): 1B02EFFC7072

after the expansion permutation E, 32 bits become 48:
   E(R)           : 7A15557A1555
   E(R) XOR K     : 6117BA866527

the 48 bits in eight groups of six, one per S-box:
   011000 010001 011110 111010 100001 100110 010100 100111

each group of six gives four bits:
   S1: bits 011000  outer 00 = row 0, inner 1100 = column 12, entry  5 = 0101
   S2: bits 010001  outer 01 = row 1, inner 1000 = column  8, entry 12 = 1100
   S3: bits 011110  outer 00 = row 0, inner 1111 = column 15, entry  8 = 1000
   S4: bits 111010  outer 10 = row 2, inner 1101 = column 13, entry  2 = 0010
   S5: bits 100001  outer 11 = row 3, inner 0000 = column  0, entry 11 = 1011
   S6: bits 100110  outer 10 = row 2, inner 0011 = column  3, entry  5 = 0101
   S7: bits 010100  outer 00 = row 0, inner 1010 = column 10, entry  9 = 1001
   S8: bits 100111  outer 11 = row 3, inner 0011 = column  3, entry  7 = 0111

the eight S-box outputs, joined: 5C82B597
after the permutation P        : 234AA9BB

every S-box takes 6 bits to 4, so it is not invertible:
   S1 row 0 column 0 gives 14 and row 1 column 1 gives 15
   48 bits in, 32 bits out: F throws away one third of what it is given.
munotes.in104

Inside a DES Round: Expansion, the S-boxes and the Permutation

Read five things out of the run.

munotes.in105

Inside a DES Round: Expansion, the S-boxes and the Permutation

E(R) is 7A15557A1555, and its 48 bits repeat in a pattern. Look at the eight groups of six: 011110 100001 010101 010101 011110 100001 010101 010101. The first four groups and the last four are identical, because R itself was F0AAF0AA, which is two identical halves. That is not a property of E; it is a property of this particular R, and it is worth noticing so that a reader does not mistake it for one.

The exclusive-or with the subkey gives 6117BA866527. From here on the values depend on the key, and this is the only place the key touches the round.

Every S-box lookup is shown with its row and column. Take S1: the six bits are 011000, the outer bits are 0 and 0 so the row is 0, the inner four bits are 1100 so the column is 12, the entry is 5, and 5 as four bits is 0101. Follow that for all eight and you can do it in an examination.

The eight outputs joined give 5C82B597, and P turns that into 234AA9BB. This is the value the previous chapter used to compute R1, and it is the value every published walk-through of DES gives for round 1 of this plaintext and key. That agreement is the check on this entire chapter.

Each S-box takes six bits to four, so it throws two away. F as a whole takes 48 bits to 32. The round function is therefore not invertible, which is exactly what the Feistel chapter said it did not need to be.

The expansion permutation, and why its rows overlap

The E table is usually printed as eight rows of six. Read it and you see that the last two entries of each row are the first two entries of the next, and that the table wraps: it begins with 32 and ends with 1.

That overlap is the point. Bit 1 of R appears in group 1 and in group 8. Bit 4 appears in group 1 and group 2. So sixteen of the 32 bits go into two S-boxes each, and a change in any one of those bits affects two S-boxes in this round, and therefore many more in the next. That is diffusion produced by duplication, and it costs nothing in hardware.

A worked example: one S-box by hand

Read S5 with the input bits 100001.

Step 1: the row. The outer bits are the first, 1, and the sixth, 1. As a two-bit number that is binary 11, which is 3. Row 3.

munotes.in106

Inside a DES Round: Expansion, the S-boxes and the Permutation

Step 2: the column. The middle four bits are 0, 0, 0, 0. As a four-bit number that is 0. Column 0.

Step 3: the entry. S5 row 3 column 0. Counting rows from 0, row 3 of S5 is 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3, and column 0 of that row is 11.

Step 4: as four bits. 11 in binary is 1011.

So 100001 goes to 1011, which is exactly what the run printed for S5. Notice how easy it would have been to take the row from the first two bits, 10, giving row 2 and the answer 0 instead of 11.

Distinctions that carry marks

StepInputOutputWhat it gives
Expansion E32 bits48 bitsdiffusion, and room for the subkey
Exclusive-or with the subkey48 and 48 bits48 bitsthe key's only entry point
The eight S-boxes48 bits32 bitsconfusion, and the only non-linearity
Permutation P32 bits32 bitsdiffusion, spreading each S-box across the next round
Expansion permutation EPermutation PInitial permutation IP
Size32 to 4832 to 3264 to 64
Duplicates bitsyes, sixteen of themnono
Whereinside Finside Foutside the rounds
Adds strengthyes, diffusionyes, diffusionno

What beginners get wrong here

Taking the S-box row from the first two bits. It is the first and the last. This one error invalidates a whole answer.

Forgetting that the S-boxes are the only non-linear part. If a question asks why the S-boxes matter, that is the answer, and "they provide confusion" is the second half of it.

Thinking E is a plain permutation. It duplicates sixteen bits, so it is an expansion; the same input bit appears twice in the output.

Confusing P with IP. P is 32 bits, inside F, and contributes diffusion. IP is 64 bits, outside the rounds, and contributes nothing.

Expecting F to be invertible. It takes 48 bits to 32 and discards information. The Feistel structure is what makes that acceptable.

Quick revision

  • F has four steps: expansion E (32 to 48), exclusive-or with the 48-bit subkey, eight S-boxes (48 to 32), permutation P (32 to 32).
  • E duplicates sixteen bits so that one input bit feeds two S-boxes; that is diffusion, and it is why its rows overlap.
  • An S-box takes 6 bits to 4. Row from the first and sixth bits; column from the middle four.
  • The S-boxes are the only non-linear part of DES. Without them the cipher is linear and solvable as a system of equations.
  • P spreads each S-box's four output bits to four different S-boxes in the next round.
  • On the standard block and key: E(R0) is 7A15557A1555, exclusive-ored with K1 it is 6117BA866527, the S-box outputs join to 5C82B597, and after P the round function gives 234AA9BB.
  • The S-box design criteria were classified in 1977; when differential cryptanalysis was published in 1990 the boxes proved unusually resistant to it.
  • F takes 48 bits to 32, so it is not invertible, which the Feistel structure does not require.
munotes.in107

Inside a DES Round: Expansion, the S-boxes and the Permutation

Test yourself

1. Describe the four steps of the DES round function, with the bit counts. The 32-bit input is expanded to 48 bits by the expansion permutation E, which duplicates sixteen bits. The 48 bits are exclusive-ored with the 48-bit subkey. The result is split into eight groups of six, and each group is replaced by four bits through its own S-box, giving 32 bits. Those 32 bits are rearranged by the permutation P.

2. How is an S-box addressed? The first and sixth bits of the six-bit group, taken as a two-bit number, give the row from 0 to 3. The middle four bits, taken as a four-bit number, give the column from 0 to 15. The table entry at that row and column, written as four bits, is the output.

3. Read S5 with the input 100001. The outer bits are 1 and 1, so the row is binary 11, which is 3. The middle bits are 0000, so the column is 0. S5 row 3 column 0 holds 11, which as four bits is 1011.

4. Why is the expansion permutation an expansion rather than a permutation? Because it produces 48 bits from 32 by using sixteen of the input bits twice. Its purpose is both to match the 48-bit subkey and to make one input bit influence two S-boxes, which spreads the effect of a change more quickly through the rounds.

5. Why are the S-boxes the most important part of DES? Because they are the only non-linear component. Every other operation in the cipher, the permutations and the exclusive-ors, is linear, and a cipher built only from linear operations can be expressed and solved as a system of linear equations. The S-boxes supply the confusion that prevents this.

6. What does the permutation P contribute, and what would happen without it? It spreads the four output bits of each S-box across four different S-boxes in the following round, so that the influence of every input bit reaches the whole block within a few rounds. Without it the diffusion would remain local to each six-bit group, and an attacker could attack each S-box almost independently.

munotes.in108

Inside a DES Round: Expansion, the S-boxes and the Permutation

7. Why is the round function not invertible, and why does that not matter? Because each S-box maps six bits to four, so F maps 48 bits to 32 and discards information. It does not matter because a Feistel structure only ever evaluates F in the forward direction: going backwards, the right half of the previous round is available immediately, F is recomputed on it, and the result is exclusive-ored out.

Contents This chapter on its own page

munotes.in109

Chapter Twenty-One

The DES Key Schedule

Syllabus topic Module 1, "Classical Encryption Techniques: The Data Encryption Standard"

In one line

Throw away the parity bits, split what is left in two, and rotate each half a little more before every round, taking 48 of the 56 bits each time. That is how one 56-bit key becomes sixteen different 48-bit subkeys.

In the wording a student can write in an examination: the DES key schedule takes the 64-bit key and applies permuted choice one, PC-1, which discards the eight parity bits and permutes the remaining 56. The 56 bits are split into two 28-bit halves, C and D. For each of the sixteen rounds, both halves are circularly left shifted by one or two bits according to a fixed schedule, and then permuted choice two, PC-2, selects 48 of the 56 shifted bits to form that round's subkey.

Why the schedule is shaped like this

The parity bits go first. In a 64-bit DES key every eighth bit carries odd parity over the preceding seven, so that a key damaged in transmission can be detected. PC-1's table has 56 entries and none of them is 8, 16, 24, 32, 40, 48, 56 or 64: the parity bits are simply not selected. That is where the 56 in "56-bit key" comes from.

The rotation makes the subkeys different. If every round used the same 48 bits the cipher would be much weaker, and one of the attacks in the next chapter would become far easier. Rotating both halves before each round means each subkey is a different selection from the key.

The shift schedule is one, one, two, two, two, two, two, two, one, two, two, two, two, two, two, one. Rounds 1, 2, 9 and 16 shift by one and the other twelve shift by two. Add them up and the total is 28, which is exactly the length of each half. So after sixteen rounds each half has been rotated all the way round and is back where it started. That is why a hardware implementation can run the schedule forwards for encryption and backwards for decryption from the same starting registers, and it is the tidiest fact in DES.

PC-2 discards eight of the 56 bits each round. So each subkey uses 48 of the 56 available bits, and which eight are left out changes from round to round because the halves have rotated.

The schedule, computed

# The DES key schedule, from Tables 6, 7 and 8 of FIPS 46-3.

PC1 = [57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18,
       10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36,
       63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22,
       14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4]

PC2 = [14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10,
       23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2,
       41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48,
       44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32]

SHIFTS = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]

def bits_of(hexstr):
    n = len(hexstr) * 4
    v = int(hexstr, 16)
    return [(v >> (n - 1 - i)) & 1 for i in range(n)]

def hexof(bl):
    v = 0
    for b in bl:
        v = (v << 1) | b
    return "%0*X" % ((len(bl) + 3) // 4, v)

def permute(bl, table):
    return [bl[i - 1] for i in table]

key = "133457799BBCDFF1"
kb = bits_of(key)
print("the 64-bit key        :", key)
print("every 8th bit is a parity bit, so 56 bits of key material remain")
print("parity of each byte   :", end=" ")
for i in range(8):
    byte = kb[i * 8:i * 8 + 8]
    print("%d" % (sum(byte) % 2), end=" ")
print("  (odd parity on every byte, as the standard requires)")
print()

k56 = permute(kb, PC1)
c, d = k56[:28], k56[28:]
print("after PC-1, the 56 bits split into two halves of 28:")
print("   C0                 :", "".join(str(b) for b in c))
print("   D0                 :", "".join(str(b) for b in d))
print()
print("round  shift  subkey (48 bits, as hex)")
for r in range(16):
    n = SHIFTS[r]
    c = c[n:] + c[:n]
    d = d[n:] + d[:n]
    k = permute(c + d, PC2)
    print("  %2d      %d     %s" % (r + 1, n, hexof(k)))
print()
print("total left shifts over the sixteen rounds:", sum(SHIFTS))
print("so C16 and D16 are back where C0 and D0 started:", sum(SHIFTS) == 28)
print()

def schedule(keyhex):
    b = bits_of(keyhex)
    k = permute(b, PC1)
    cc, dd = k[:28], k[28:]
    out = []
    for r in range(16):
        n = SHIFTS[r]
        cc = cc[n:] + cc[:n]
        dd = dd[n:] + dd[:n]
        out.append(hexof(permute(cc + dd, PC2)))
    return out

print("the four weak keys: every one of their sixteen subkeys is the same,")
print("so encrypting twice returns the plaintext.")
for wk in ("0101010101010101", "FEFEFEFEFEFEFEFE",
           "E0E0E0E0F1F1F1F1", "1F1F1F1F0E0E0E0E"):
    subs = schedule(wk)
    print("   %s  all 16 subkeys equal: %-5s  (K1 = %s)"
          % (wk, len(set(subs)) == 1, subs[0]))
munotes.in110

The DES Key Schedule

the 64-bit key        : 133457799BBCDFF1
every 8th bit is a parity bit, so 56 bits of key material remain
parity of each byte   : 1 1 1 1 1 1 1 1   (odd parity on every byte, as the standard requires)

after PC-1, the 56 bits split into two halves of 28:
   C0                 : 1111000011001100101010101111
   D0                 : 0101010101100110011110001111

round  shift  subkey (48 bits, as hex)
   1      1     1B02EFFC7072
   2      1     79AED9DBC9E5
   3      2     55FC8A42CF99
   4      2     72ADD6DB351D
   5      2     7CEC07EB53A8
   6      2     63A53E507B2F
   7      2     EC84B7F618BC
   8      2     F78A3AC13BFB
   9      1     E0DBEBEDE781
  10      2     B1F347BA464F
  11      2     215FD3DED386
  12      2     7571F59467E9
  13      2     97C5D1FABA41
  14      2     5F43B7F2E73A
  15      2     BF918D3D3F0A
  16      1     CB3D8B0E17F5

total left shifts over the sixteen rounds: 28
so C16 and D16 are back where C0 and D0 started: True

the four weak keys: every one of their sixteen subkeys is the same,
so encrypting twice returns the plaintext.
   0101010101010101  all 16 subkeys equal: True   (K1 = 000000000000)
   FEFEFEFEFEFEFEFE  all 16 subkeys equal: True   (K1 = FFFFFFFFFFFF)
   E0E0E0E0F1F1F1F1  all 16 subkeys equal: True   (K1 = FFFFFF000000)
   1F1F1F1F0E0E0E0E  all 16 subkeys equal: True   (K1 = 000000FFFFFF)
munotes.in111

The DES Key Schedule

Read five things out of the run.

Every byte of the key has odd parity, printed as eight 1s. That is what makes 133457799BBCDFF1 a well-formed DES key. A key whose parity is wrong is not rejected by the algorithm, which ignores those bits entirely, but it would be rejected by equipment that checks.

C0 and D0 are 1111000011001100101010101111 and 0101010101100110011110001111. These are the values every published walk-through gives, so this chapter is checkable against an independent source.

K1 is 1B02EFFC7072. This is the subkey the previous chapter used, and the round function it produced there came out 234AA9BB, which agrees with the published value. The two chapters therefore check each other.

The sixteen subkeys are all different, and they have to be. Two identical subkeys in a Feistel cipher create a symmetry an attacker can exploit, and the weak keys below are the extreme case of exactly that.

The shifts total 28. The program checked it and printed True. Each half is 28 bits, so sixteen rounds of shifting brings both halves round to their starting position.

The weak keys, and why they are weak

The last block of the run is the whole answer to a question that is set often.

A weak key is one for which all sixteen subkeys are identical. The run found that for all four of the classical weak keys, 0101010101010101, FEFEFEFEFEFEFEFE, E0E0E0E0F1F1F1F1 and 1F1F1F1F0E0E0E0E, the set of sixteen subkeys has exactly one member.

Why does that matter? Because DES decryption is the same algorithm with the subkeys reversed. If all sixteen subkeys are the same, then the reversed schedule is the same as the forward schedule, so decryption is identical to encryption. The consequences follow at once.

  • Encrypting a block twice with a weak key returns the original block. The key is its own inverse.
  • An attacker who suspects a weak key can test all four in four operations.
  • In some protocols, a self-inverse cipher destroys a security property the protocol was relying on.
munotes.in112

The DES Key Schedule

The reason those four keys behave this way is visible in their structure. 0101010101010101 is all zero bits apart from parity, so C0 and D0 are all zeros and rotating a block of zeros changes nothing. FEFEFEFEFEFEFEFE is all ones apart from parity, and the same applies. The other two give one half of all zeros and one half of all ones.

There are also semi-weak key pairs, sixteen keys forming six pairs, for which encryption under one key is decryption under the other, because their subkey schedules are each other's reverse. And a larger set of possibly weak keys produce only four distinct subkeys rather than sixteen.

How much does this cost DES? Almost nothing: there are 4 weak keys, 12 semi-weak, and 48 possibly weak, out of 2 to the power 56. The chance of choosing one at random is negligible. It is examinable because it is a clean illustration of what a key schedule is for, not because it is a practical threat.

A worked example: the first shift, by hand

The setting. Key 133457799BBCDFF1. After PC-1, from the run, C0 is 1111000011001100101010101111 and D0 is 0101010101100110011110001111.

Step 1: round 1 shifts by one. Rotate each half one place left, moving the leftmost bit round to the right-hand end.

C1 is 1110000110011001010101011111 and D1 is 1010101011001100111100011110.

Check C1 against C0: C0 began 1111 0000 and C1 begins 1110 0001, which is C0 with its first bit moved to the end. And C0 ended 1111 while C1 ends 1111 1, having gained the 1 that came round. That is the check to make, because a rotation done wrong is the commonest arithmetic slip in this topic.

Step 2: apply PC-2. Concatenate C1 and D1 into 56 bits, then take the 48 bits PC-2 names, in the order it names them. PC-2 begins 14, 17, 11, 24, 1, 5, so the subkey's first six bits are bits 14, 17, 11, 24, 1 and 5 of that 56-bit string.

Step 3: read the answer. The result is 1B02EFFC7072, as the run printed for round 1.

Step 4: note what PC-2 leaves out. PC-2 has 48 entries out of 56, so eight positions are never named. Check the table and they are 9, 18, 22, 25, 35, 38, 43 and 54. Those eight bits of C1 D1 are simply not used in round 1, and because the halves rotate, a different eight are unused in round 2.

Distinctions that carry marks

PC-1PC-2
Input64 bits, the whole key56 bits, the shifted halves
Output56 bits48 bits
Discardsthe 8 parity bits8 of the 56, a different 8 each round
Appliedonceonce per round, sixteen times
munotes.in113

The DES Key Schedule

Weak keySemi-weak key pairPossibly weak key
Subkeysall 16 identicalthe two schedules are each other's reverseonly 4 distinct
Consequenceencryption is decryption; the key is its own inverseencrypting with one decrypts the othera reduced schedule
How many412, in 6 pairs48
The 64-bit keyThe 56-bit key material
Contains56 key bits and 8 parity bitsthe bits PC-1 selects
Keyspacenot 2 to the power 642 to the power 56
Parityodd, per bytenot applicable

What beginners get wrong here

Saying the keyspace is 2 to the power 64. PC-1 discards the parity bits, so it is 2 to the power 56.

Rotating the two halves together. C and D are rotated separately, each within its own 28 bits.

Rotating right, or shifting instead of rotating. It is a circular left shift: the bit that leaves the left end comes back at the right.

Getting the shift schedule wrong. Rounds 1, 2, 9 and 16 shift by one; all the others by two. A useful check is that the total must be 28.

Listing the weak keys without saying why they are weak. The reason is that all sixteen subkeys are identical, so decryption is the same as encryption and the key is its own inverse.

Quick revision

  • PC-1: 64 bits to 56, discarding the eight parity bits. That is where 56 comes from.
  • Split into C and D, 28 bits each, rotated separately and circularly left before every round.
  • Shift schedule: 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1. Rounds 1, 2, 9 and 16 shift by one.
  • The shifts total 28, so after sixteen rounds both halves are back where they started.
  • PC-2: 56 bits to 48, leaving out eight, and a different eight each round because the halves have rotated.
  • For key 133457799BBCDFF1: C0 = 1111000011001100101010101111, D0 = 0101010101100110011110001111, K1 = 1B02EFFC7072.
  • A weak key has all sixteen subkeys identical, so encryption equals decryption and the key is its own inverse. There are 4 of them, plus 12 semi-weak in 6 pairs and 48 possibly weak.
  • The keyspace is 2 to the power 56, never 2 to the power 64.

Test yourself

1. Describe the DES key schedule. PC-1 takes the 64-bit key to 56 bits, discarding the eight parity bits. The 56 bits split into two 28-bit halves C and D. Before each of the sixteen rounds both halves are circularly left shifted, by one bit in rounds 1, 2, 9 and 16 and by two bits otherwise, and PC-2 then selects 48 of the 56 shifted bits as that round's subkey.

munotes.in114

The DES Key Schedule

2. Why is the DES key 56 bits when it is presented as 64? Because every eighth bit is a parity bit, carrying odd parity over the preceding seven so that a corrupted key can be detected. PC-1 does not select any of those eight positions, so they contribute nothing to the subkeys and the keyspace is 2 to the power 56.

3. State the shift schedule and give the check on it. One, one, two, two, two, two, two, two, one, two, two, two, two, two, two, one: rounds 1, 2, 9 and 16 shift by one and the remaining twelve by two. The check is that the shifts total 28, the length of each half, so after sixteen rounds C and D have been rotated exactly once round and are back at their starting positions.

4. What is a weak key, and why is it weak? A key for which all sixteen subkeys come out identical. Since DES decryption is the same algorithm with the subkeys reversed, an identical schedule means decryption is the same as encryption, so the key is its own inverse and encrypting a block twice returns it. There are four such keys.

5. Name the four weak keys. 0101010101010101, FEFEFEFEFEFEFEFE, E0E0E0E0F1F1F1F1 and 1F1F1F1F0E0E0E0E.

6. Distinguish a weak key from a semi-weak key pair. A weak key has all sixteen subkeys the same, so encryption and decryption coincide under that one key. A semi-weak pair consists of two keys whose subkey schedules are each other's reverse, so encrypting with one key is the same as decrypting with the other. There are four weak keys and twelve semi-weak keys forming six pairs.

7. How many bits does PC-2 discard, and why is that acceptable? Eight of the 56, so each subkey uses 48 bits. It is acceptable because the halves rotate between rounds, so a different eight bits are omitted in each round and every bit of the key material is used in most rounds.

Contents This chapter on its own page

munotes.in115

Chapter Twenty-Two

DES Run End to End: Decryption, and the Avalanche Effect

Syllabus topic Module 1, "Classical Encryption Techniques: The Data Encryption Standard"

In one line

Decryption is encryption with the subkeys used backwards, and one changed bit anywhere changes about half the ciphertext. The first fact makes DES cheap in hardware; the second is what makes it a cipher rather than a code.

In the wording a student can write in an examination: DES decryption uses the same algorithm as encryption, with the sixteen subkeys applied in reverse order, K16 first and K1 last. The avalanche effect is the property that a small change in either the plaintext or the key produces a large change in the ciphertext: in DES, a change of one bit in the plaintext or in the key changes about half the bits of the ciphertext. A cipher without a strong avalanche effect is weak, because an attacker can then learn about the plaintext or the key by watching how the ciphertext responds to small changes.

The cipher, in one file

Everything in the three preceding chapters is here in one place: the two outer permutations, the expansion, the eight S-boxes, the permutation P, the key schedule and the sixteen rounds. Every table is transcribed from FIPS 46-3, and the tables are 1-based exactly as the standard prints them, which is why permute subtracts one.

IP = [58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4,
      62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8,
      57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
      61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7]

FP = [40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31,
      38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29,
      36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27,
      34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25]

E = [32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13,
     12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21,
     22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1]

P = [16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10,
     2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25]

PC1 = [57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18,
       10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36,
       63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22,
       14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4]

PC2 = [14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10,
       23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2,
       41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48,
       44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32]

SHIFTS = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]

S = [
 [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7],
  [0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8],
  [4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0],
  [15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]],
 [[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10],
  [3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5],
  [0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15],
  [13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]],
 [[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8],
  [13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1],
  [13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7],
  [1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]],
 [[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15],
  [13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9],
  [10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4],
  [3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]],
 [[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9],
  [14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6],
  [4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14],
  [11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]],
 [[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11],
  [10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8],
  [9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6],
  [4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]],
 [[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1],
  [13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6],
  [1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2],
  [6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]],
 [[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7],
  [1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2],
  [7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8],
  [2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]],
]

WEAK_KEYS = ['0101010101010101', 'FEFEFEFEFEFEFEFE',
             'E0E0E0E0F1F1F1F1', '1F1F1F1F0E0E0E0E']


def bits(data):
    """Bytes to a list of bits, most significant first."""
    return [(b >> (7 - i)) & 1 for b in data for i in range(8)]


def unbits(bl):
    out = bytearray()
    for i in range(0, len(bl), 8):
        v = 0
        for b in bl[i:i + 8]:
            v = (v << 1) | b
        out.append(v)
    return bytes(out)


def permute(bl, table):
    return [bl[i - 1] for i in table]


def left_shift(bl, n):
    return bl[n:] + bl[:n]


def xor(a, b):
    return [x ^ y for x, y in zip(a, b)]


def key_schedule(key):
    """The sixteen 48-bit subkeys, and the C and D halves at every round."""
    kb = permute(bits(key), PC1)
    c, d = kb[:28], kb[28:]
    subkeys, trace = [], []
    for r in range(16):
        c = left_shift(c, SHIFTS[r])
        d = left_shift(d, SHIFTS[r])
        k = permute(c + d, PC2)
        subkeys.append(k)
        trace.append((r + 1, SHIFTS[r], list(c), list(d), k))
    return subkeys, trace


def f(right, subkey):
    """The DES round function: expand, XOR the key, eight S-boxes, permute."""
    x = xor(permute(right, E), subkey)
    out = []
    for i in range(8):
        block = x[i * 6:i * 6 + 6]
        row = block[0] * 2 + block[5]
        col = (block[1] * 8 + block[2] * 4 + block[3] * 2 + block[4])
        v = S[i][row][col]
        out += [(v >> 3) & 1, (v >> 2) & 1, (v >> 1) & 1, v & 1]
    return permute(out, P)


def _rounds(block, subkeys):
    b = permute(bits(block), IP)
    left, right = b[:32], b[32:]
    trace = [(0, list(left), list(right))]
    for r in range(16):
        left, right = right, xor(left, f(right, subkeys[r]))
        trace.append((r + 1, list(left), list(right)))
    return unbits(permute(right + left, FP)), trace


def encrypt_block(block, key):
    subkeys, _ = key_schedule(key)
    return _rounds(block, subkeys)[0]


def decrypt_block(block, key):
    subkeys, _ = key_schedule(key)
    return _rounds(block, subkeys[::-1])[0]


def encrypt_block_trace(block, key):
    subkeys, ktrace = key_schedule(key)
    out, trace = _rounds(block, subkeys)
    return out, trace, ktrace, subkeys


def parity_ok(key):
    """Every byte of a DES key carries odd parity in its low bit."""
    return all(bin(b).count('1') % 2 == 1 for b in key)


def set_parity(key):
    out = bytearray()
    for b in key:
        b &= 0xFE
        out.append(b if bin(b).count('1') % 2 == 1 else b | 1)
    return bytes(out)
munotes.in116

DES Run End to End: Decryption, and the Avalanche Effect

The whole cipher run, and decrypted back

import des as D

KEY = bytes.fromhex("133457799BBCDFF1")
PT = bytes.fromhex("0123456789ABCDEF")

def hx(bl):
    return D.unbits(bl).hex().upper()

ct, trace, ktrace, subkeys = D.encrypt_block_trace(PT, KEY)
print("plaintext :", PT.hex().upper())
print("key       :", KEY.hex().upper())
print()
print("round      L                R")
for r, l, rr in trace:
    print("  %2d     %s         %s" % (r, hx(l), hx(rr)))
print("  swap the halves, then the final permutation")
print("ciphertext:", ct.hex().upper())
print()
back = D.decrypt_block(ct, KEY)
print("decryption is the same algorithm with the subkeys reversed")
print("recovered :", back.hex().upper(), " equals the plaintext:", back == PT)
print()
print("the first three subkeys forwards :", " ".join(hx(k) for k in subkeys[:3]))
print("the first three used in decryption:", " ".join(hx(k) for k in subkeys[::-1][:3]))
print()

def bitcount(a, b):
    return sum(bin(x ^ y).count("1") for x, y in zip(a, b))

def flip_plaintext_bit(block, i):
    b = bytearray(block)
    b[i // 8] ^= 1 << (7 - (i % 8))
    return bytes(b)

print("the avalanche effect: ONE plaintext bit changed")
alt = flip_plaintext_bit(PT, 0)
print("   original plaintext :", PT.hex().upper())
print("   one bit flipped    :", alt.hex().upper(), " bits differing:", bitcount(PT, alt))
_, t2, _, _ = D.encrypt_block_trace(alt, KEY)
print("   round   bits differing out of 64")
for r in range(0, 17):
    a = trace[r][1] + trace[r][2]
    b = t2[r][1] + t2[r][2]
    print("     %2d      %2d" % (r, sum(1 for x, y in zip(a, b) if x != y)))
ct2 = D.encrypt_block(alt, KEY)
print("   ciphertexts differ in", bitcount(ct, ct2), "of 64 bits")
print()

print("the avalanche effect: ONE key bit changed")
altkey = bytearray(KEY)
altkey[0] ^= 0b00000010          # bit 7, not a parity bit
altkey = bytes(altkey)
print("   original key       :", KEY.hex().upper())
print("   one bit flipped    :", altkey.hex().upper(), " bits differing:", bitcount(KEY, altkey))
_, t3, _, _ = D.encrypt_block_trace(PT, altkey)
print("   round   bits differing out of 64")
for r in range(0, 17):
    a = trace[r][1] + trace[r][2]
    b = t3[r][1] + t3[r][2]
    print("     %2d      %2d" % (r, sum(1 for x, y in zip(a, b) if x != y)))
ct3 = D.encrypt_block(PT, altkey)
print("   ciphertexts differ in", bitcount(ct, ct3), "of 64 bits")
print()

print("averaged over 200 single-bit plaintext changes on random blocks and keys:")
import random
random.seed(7)
total = 0
for _ in range(200):
    p = bytes(random.randrange(256) for _ in range(8))
    k = D.set_parity(bytes(random.randrange(256) for _ in range(8)))
    i = random.randrange(64)
    c1 = D.encrypt_block(p, k)
    c2 = D.encrypt_block(flip_plaintext_bit(p, i), k)
    total += bitcount(c1, c2)
print("   mean bits changed in the ciphertext: %.2f of 64" % (total / 200))
print("   an ideal cipher would average 32.00")
munotes.in117

DES Run End to End: Decryption, and the Avalanche Effect

plaintext : 0123456789ABCDEF
key       : 133457799BBCDFF1

round      L                R
   0     CC00CCFF         F0AAF0AA
   1     F0AAF0AA         EF4A6544
   2     EF4A6544         CC017709
   3     CC017709         A25C0BF4
   4     A25C0BF4         77220045
   5     77220045         8A4FA637
   6     8A4FA637         E967CD69
   7     E967CD69         064ABA10
   8     064ABA10         D5694B90
   9     D5694B90         247CC67A
  10     247CC67A         B7D5D7B2
  11     B7D5D7B2         C5783C78
  12     C5783C78         75BD1858
  13     75BD1858         18C3155A
  14     18C3155A         C28C960D
  15     C28C960D         43423234
  16     43423234         0A4CD995
  swap the halves, then the final permutation
ciphertext: 85E813540F0AB405

decryption is the same algorithm with the subkeys reversed
recovered : 0123456789ABCDEF  equals the plaintext: True

the first three subkeys forwards : 1B02EFFC7072 79AED9DBC9E5 55FC8A42CF99
the first three used in decryption: CB3D8B0E17F5 BF918D3D3F0A 5F43B7F2E73A

the avalanche effect: ONE plaintext bit changed
   original plaintext : 0123456789ABCDEF
   one bit flipped    : 8123456789ABCDEF  bits differing: 1
   round   bits differing out of 64
      0       1
      1       7
      2      21
      3      34
      4      32
      5      27
      6      27
      7      25
      8      26
      9      25
     10      27
     11      33
     12      33
     13      32
     14      35
     15      37
     16      33
   ciphertexts differ in 33 of 64 bits

the avalanche effect: ONE key bit changed
   original key       : 133457799BBCDFF1
   one bit flipped    : 113457799BBCDFF1  bits differing: 1
   round   bits differing out of 64
      0       0
      1       0
      2       3
      3      11
      4      21
      5      27
      6      27
      7      32
      8      31
      9      25
     10      28
     11      31
     12      35
     13      39
     14      35
     15      35
     16      38
   ciphertexts differ in 38 of 64 bits

averaged over 200 single-bit plaintext changes on random blocks and keys:
   mean bits changed in the ciphertext: 32.60 of 64
   an ideal cipher would average 32.00
munotes.in118

DES Run End to End: Decryption, and the Avalanche Effect

Reading the trace

Every round is checkable. L0 is CC00CCFF and R0 is F0AAF0AA, from the chapter on the shape of DES. L1 is R0, as the Feistel equation requires. R1 is EF4A6544, which is L0 exclusive-ored with the 234AA9BB that the round-function chapter computed. So the three earlier chapters and this one agree, and all of them agree with the published walk-through of this plaintext and key.

munotes.in119

DES Run End to End: Decryption, and the Avalanche Effect

The last round is not swapped in the trace. After round 16 the trace shows L16 = 43423234 and R16 = 0A4CD995. The ciphertext is formed from R16 L16, in that order, passed through the final permutation. That is the 32-bit swap, and it is printed as its own line so it cannot be missed.

The decryption subkeys are the encryption subkeys reversed. The run prints the first three of each: forwards they begin 1B02EFFC7072, and in decryption they begin CB3D8B0E17F5, which is K16. Nothing else about the algorithm changes.

munotes.in120

DES Run End to End: Decryption, and the Avalanche Effect

Why decryption is the same algorithm

The argument was given in the Feistel chapter and is worth restating in DES's own terms, because it is a five-mark question.

Encryption takes the permuted block as L0 R0, runs sixteen rounds, swaps, and applies FP. Since FP is the inverse of IP, feeding the ciphertext back in undoes FP at once and presents the algorithm with R16 L16.

Now call that the decryption round 0, so its left half is R16 and its right half is L16. Apply the round with subkey K16. The new left half is the old right half, which is L16, and L16 equals R15 because that is what round 16 of encryption made it. The new right half is R16 XOR F(L16, K16), and R16 was L15 XOR F(R15, K16), so the exclusive-or cancels and what remains is L15. After one decryption round the halves hold R15 and L15.

That is the same relationship one step earlier, so the argument repeats fifteen more times, and after sixteen decryption rounds the halves hold R0 and L0. The final swap puts them in the order L0 R0, and FP undoes the original IP. The plaintext is back, and F was never inverted.

The avalanche effect, measured

The second half of the run is the measurement, and it is worth reading closely.

One plaintext bit changed. The plaintext went from 0123456789ABCDEF to 8123456789ABCDEF, a difference of exactly one bit. After round 0 the two states differ in 1 bit, as they must. After round 1, 7 bits. After round 2, 21. After round 3, 34, and from there the figure sits between 25 and 37 for the rest of the cipher, ending at 33 of 64 in the ciphertext.

Read what happened between rounds 1 and 3. One bit became seven, then twenty-one, then thirty-four. Three rounds took a single-bit difference to half the block. That is diffusion doing exactly the job the Feistel chapter described, and the mechanism is visible in the earlier chapters: the expansion permutation sends each changed bit into two S-boxes, each S-box changes up to four output bits, and the permutation P scatters those four bits into four different S-boxes for the next round.

One key bit changed. The key went from 133457799BBCDFF1 to 113457799BBCDFF1. Note that the bit chosen is not a parity bit, because flipping a parity bit would change nothing at all: DES ignores them. Here the spread starts more slowly, 0 bits after round 1, because that key bit does not appear in K1 at all, then 3, 11, 21, and it reaches 32 by round 7. The ciphertexts differ in 38 of 64 bits.

munotes.in121

DES Run End to End: Decryption, and the Avalanche Effect

Averaged over 200 trials on random plaintexts and random keys, the mean is 32.60 of 64. An ideal cipher, in which each ciphertext bit is an independent fair coin, would average exactly 32. DES is within two-thirds of a bit of that, which is the honest way to state the avalanche effect: not "about half the bits change", which is vague, but 32.60 against an ideal 32.00, with the measurement in front of you.

What the numbers do NOT say

Two cautions, because a measured number invites over-reading.

A good avalanche does not make a cipher strong. It is a necessary property, not a sufficient one. DES has an excellent avalanche effect and a 56-bit key, and the key is what killed it. The next chapter is about that.

The per-round figures depend on which bit was flipped. Flip a different plaintext bit and the round-1 count will not be 7. The averaged figure over 200 random trials is the one that generalises; a single trace is an illustration.

A worked example: why a parity bit changes nothing

The claim. Flipping bit 8 of the DES key, which is the parity bit of the first byte, leaves the ciphertext completely unchanged.

Step 1: where the parity bits go. The key schedule chapter established that PC-1 has 56 entries and none of them is 8, 16, 24, 32, 40, 48, 56 or 64.

Step 2: what follows. Every subkey is derived from C and D, which are derived from PC-1's output. A bit that PC-1 never selects therefore reaches no subkey.

Step 3: the consequence. Two keys differing only in parity bits produce identical subkey schedules and identical ciphertexts. So the 64-bit key space of 2 to the power 64 collapses to 2 to the power 56 distinct behaviours, in groups of 256 keys that all behave identically.

Step 4: why the avalanche run avoided it. The listing flipped bit 7 of the first byte, not bit 8, precisely so that the measurement would be of a key bit and not of a bit the cipher ignores. Flipping bit 8 would have printed 0 for every round and 0 for the ciphertext, which would have looked like a broken cipher rather than a correct one.

Distinctions that carry marks

EncryptionDecryption
Algorithmthe sixteen rounds, swap, FPthe same
SubkeysK1 to K16K16 to K1
Hardwareone circuitthe same circuit
Round functionrun forwardsrun forwards
Avalanche from a plaintext bitAvalanche from a key bit
After round 17 bits differ0 bits differ
Reaches about halfby round 3by round 7
In the ciphertext33 of 6438 of 64
Why the differencethe bit enters the data path at oncethe bit may not appear in the first subkeys
munotes.in122

DES Run End to End: Decryption, and the Avalanche Effect

Avalanche effectCompleteness
Saysa small input change gives a large output changeevery output bit depends on every input bit
Measured bycounting differing bitschecking dependence
DES32.60 of 64 on averageyes, after a few rounds

What beginners get wrong here

Saying DES has a separate decryption algorithm. It has one algorithm. Only the order of the subkeys changes.

Forgetting that the ciphertext is R16 L16 and not L16 R16. The swap after round 16 is what makes the reversed schedule work.

Flipping a parity bit and concluding the cipher is broken. DES ignores the eight parity bits entirely.

Saying "about half the bits change" and stopping. Give the number. One plaintext bit gives 33 of 64 here, and 32.60 of 64 averaged over 200 trials against an ideal 32.00.

Treating a strong avalanche as proof of security. It is necessary and not sufficient, and DES is the standing counter-example.

Quick revision

  • Decryption is encryption with the subkeys reversed, K16 first. The same circuit does both.
  • Why: R(i-1) = L(i) directly, then F is recomputed forwards and exclusive-ored out. The 32-bit swap after round 16 is what lines it up.
  • The ciphertext is assembled from R16 L16, then FP.
  • Full trace for 0123456789ABCDEF under 133457799BBCDFF1: L0 = CC00CCFF, R0 = F0AAF0AA, R1 = EF4A6544, ciphertext 85E813540F0AB405.
  • Avalanche effect: a small change in plaintext or key changes about half the ciphertext bits.
  • Measured: one plaintext bit gives 1, 7, 21, 34 bits differing after rounds 0 to 3 and 33 of 64 in the ciphertext; one key bit gives 0, 0, 3, 11 and 38 of 64.
  • Mean over 200 random trials: 32.60 of 64, against an ideal cipher's 32.00.
  • A key bit spreads more slowly at first because it may not appear in the early subkeys.
  • Parity bits change nothing: PC-1 never selects them, so 256 keys share every behaviour.
  • A strong avalanche is necessary but not sufficient: DES has one and a 56-bit key.

Test yourself

1. How is DES decrypted? By the same algorithm used for encryption, with the sixteen subkeys applied in reverse order, K16 in the first round through to K1 in the sixteenth, including the same 32-bit swap and the same outer permutations.

2. Prove that reversing the subkeys decrypts. Feeding the ciphertext in undoes FP, presenting the algorithm with R16 L16. In the first decryption round the new left half is L16, which equals R15; the new right half is R16 XOR F(L16, K16), and since R16 was L15 XOR F(R15, K16) and L16 equals R15, the two F terms cancel and what remains is L15. So after one decryption round the halves hold R15 and L15, which is the same relation one round earlier, and the argument repeats to R0 and L0.

munotes.in123

DES Run End to End: Decryption, and the Avalanche Effect

3. Define the avalanche effect and say why a cipher needs it. That a small change in the plaintext or in the key produces a large change in the ciphertext. Without it, an attacker could vary the input slightly and watch the output respond in a predictable way, learning about the plaintext or narrowing the key; a strong avalanche makes the ciphertext's response to a small change indistinguishable from a fresh random value.

4. In this chapter's measurement, how many ciphertext bits changed when one plaintext bit was flipped, and what is the average? 33 of 64 for that particular flip. Averaged over 200 single-bit changes on random plaintexts and random keys the mean was 32.60 of 64, against the 32.00 an ideal cipher would give.

5. Why did the key-bit avalanche show 0 differing bits after round 1? Because the key bit that was flipped does not appear in K1. PC-2 selects 48 of the 56 bits for each subkey, so eight are omitted in any given round, and a changed bit has no effect until a round whose subkey includes it. It reached about half the block by round 7.

6. What happens if you flip a parity bit of a DES key? Nothing. PC-1 selects 56 of the 64 key bits and never selects positions 8, 16, 24, 32, 40, 48, 56 or 64, so a parity bit reaches no subkey. Two keys differing only in parity bits give identical ciphertexts, which is why the keyspace is 2 to the power 56 and not 2 to the power 64.

7. Does a strong avalanche effect make a cipher secure? Justify with an example. No. It is a necessary property but not a sufficient one. DES has an excellent avalanche effect, measured here at 32.60 of 64 against an ideal 32.00, and was withdrawn in 2005 because its 56-bit key can be searched exhaustively. Avalanche says nothing about keyspace.

Contents This chapter on its own page

munotes.in124

Chapter Twenty-Three

The Strength of DES: Fifty-six Bits, and the Machines That Broke It

Syllabus topic Module 1, "Classical Encryption Techniques: The Strength of DES"

In one line

Fifty-six bits was never the problem in 1977 and was the only problem by 1998. Nobody ever found a practical weakness in DES's design; the keyspace simply became searchable.

In the wording a student can write in an examination: the strength of DES rests on two questions. The first is the key length: a 56-bit key gives 2 to the power 56 possible keys, and exhaustive search succeeds after half of them on average. The second is the nature of the algorithm: whether cryptanalysis can do better than exhaustive search. On the first question DES failed, publicly and repeatedly, between 1997 and 1999. On the second it has held up remarkably well: differential and linear cryptanalysis both work in theory and neither has ever been practical against the full sixteen rounds.

The concern from the very beginning

When DES was adopted in 1977 the key length was already contested. IBM's submission had a larger key and the standard adopted 56 bits, and the two best-known critics of the time, Whitfield Diffie and Martin Hellman, argued that a machine could be built to search the keyspace. They were arguing about cost, not about mathematics, and the whole subsequent history is the cost falling.

That is the shape of the answer to "discuss the strength of DES": one criticism was about the key and was correct; the other was about the classified S-box design criteria and turned out to be unfounded.

What the machines actually did

These are the RSA Laboratories DES Challenges, and the figures below are the participants' own.

ChallengeDateWhoTime
DES-I17 June 1997the DESCHALL volunteer effortabout 96 days
DES-II-1completed 23 February 1998distributed.netabout 41 days
DES-II-217 July 1998the EFF DES Cracker56 hours
DES-III18 to 19 January 1999the EFF DES Cracker with distributed.netunder 24 hours

Two of those rows deserve their own sentences.

The EFF DES Cracker, July 1998. The Electronic Frontier Foundation built a purpose-made machine out of an ordinary personal computer driving an array of custom chips. Its own press release states that it cost less than $250,000, that it searched 88 thousand million keys every second, and that "in less than 3 days of searching, the EFF DES Cracker found the correct key", after 56 hours. The EFF's stated purpose was not to break anybody's traffic but to settle a public argument: officials had been asserting that DES was adequate, and a quarter of a million dollars of hardware demonstrated otherwise.

DES-III, January 1999. distributed.net's own page records that the contest started at 9am Pacific time on 18 January 1999 and ended successfully at 7am on the 19th, "less than 24 hours", with the EFF machine working alongside about a hundred thousand volunteer computers.

munotes.in125

The Strength of DES: Fifty-six Bits, and the Machines That Broke It

The arithmetic, computed

# What the published DES breaks actually cost, worked from their own figures.

KEYSPACE = 2 ** 56
AVERAGE = KEYSPACE // 2

print("the DES keyspace           : 2 to the power 56 =", format(KEYSPACE, ","))
print("expected keys before a hit : half of it        =", format(AVERAGE, ","))
print()

# The EFF DES Cracker's own published rate: 88 thousand million keys a second.
eff_rate = 88 * 10 ** 9
secs = AVERAGE / eff_rate
print("EFF DES Cracker, 88 thousand million keys a second:")
print("   expected time           : %.1f hours" % (secs / 3600))
print("   and it reported         : 56 hours for the actual key it found")
print("   so the key it found was : %.2f of the way through the keyspace"
      % (56 * 3600 * eff_rate / KEYSPACE))
print()

# DES-III: under 24 hours. What rate does that imply?
for hours, label in ((22.25, "DES-III, 22 hours 15 minutes"),
                     (24.0, "DES-III, taking the full 24 hours")):
    rate = AVERAGE / (hours * 3600)
    print("%s implies about %s keys a second" % (label, format(int(rate), ",")))
print()

# What one ordinary machine would take today, at a generous rate.
for rate, label in ((10 ** 7, "a 1998 desktop, 10 million keys a second"),
                    (10 ** 9, "one modern core, 1 thousand million a second"),
                    (10 ** 12, "a graphics card, 1 million million a second")):
    years = AVERAGE / rate / (365.25 * 24 * 3600)
    if years < 1:
        print("%-50s %.1f days" % (label, years * 365.25))
    else:
        print("%-50s %.1f years" % (label, years))
print()

print("and the two cryptanalytic attacks, in plaintext rather than in time:")
for name, n, kind in (("differential cryptanalysis", 47, "chosen plaintexts"),
                      ("linear cryptanalysis", 47, "known plaintexts")):
    blocks = 2 ** n
    tb = blocks * 8 / 10 ** 12
    print("   %-28s 2 to the power %d %s = %s blocks = %s terabytes"
          % (name, n, kind, format(blocks, ","), format(int(tb), ",")))
print("   a DES block is 8 bytes, so both attacks need more data under ONE key")
print("   than any real system has ever encrypted.")
the DES keyspace           : 2 to the power 56 = 72,057,594,037,927,936
expected keys before a hit : half of it        = 36,028,797,018,963,968

EFF DES Cracker, 88 thousand million keys a second:
   expected time           : 113.7 hours
   and it reported         : 56 hours for the actual key it found
   so the key it found was : 0.25 of the way through the keyspace

DES-III, 22 hours 15 minutes implies about 449,797,715,592 keys a second
DES-III, taking the full 24 hours implies about 416,999,965,497 keys a second

a 1998 desktop, 10 million keys a second           114.2 years
one modern core, 1 thousand million a second       1.1 years
a graphics card, 1 million million a second        0.4 days

and the two cryptanalytic attacks, in plaintext rather than in time:
   differential cryptanalysis   2 to the power 47 chosen plaintexts = 140,737,488,355,328 blocks = 1,125 terabytes
   linear cryptanalysis         2 to the power 47 known plaintexts = 140,737,488,355,328 blocks = 1,125 terabytes
   a DES block is 8 bytes, so both attacks need more data under ONE key
   than any real system has ever encrypted.
munotes.in126

The Strength of DES: Fifty-six Bits, and the Machines That Broke It

Read five things out of that run.

56 hours is not the average, and this is the detail that matters. At its own published rate of 88 thousand million keys a second, the EFF machine's expected time was 113.7 hours. It finished in 56 because the key it was looking for happened to sit about a quarter of the way through the keyspace. So the honest figure for what that machine could do is about five days on average, not 56 hours, and a student who quotes 56 hours as DES's strength has understated it by a factor of two. Both numbers are worth knowing, and knowing which is which is what distinguishes an answer.

DES-III implies a rate of about 450 thousand million keys a second. That is five times the EFF machine alone, which is what adding a hundred thousand volunteer computers bought.

One modern processor core would take 1.1 years. So DES is not breakable on a laptop in an afternoon, and it is entirely breakable by anybody with a graphics card: the run shows 0.4 days. That is the practical statement to make about DES in 2026.

The two cryptanalytic attacks are costed in data, not in time. Differential cryptanalysis of the full sixteen rounds needs about 2 to the power 47 chosen plaintexts, and Matsui's linear cryptanalysis needs about 2 to the power 47 known plaintexts. The program turns that into 140,737,488,355,328 blocks, which at 8 bytes a block is 1,125 terabytes under a single key. No real system has ever encrypted that much under one DES key, and an attacker who could choose 1,125 terabytes of plaintext has access that makes the cipher irrelevant.

So the practical break was always brute force. That is the sentence to take away, and it generalises: when a cipher dies, it is usually the key length that kills it.

The three cryptanalytic attacks worth naming

Differential cryptanalysis, published by Eli Biham and Adi Shamir between 1990 and 1993. It looks at how a difference between two plaintexts propagates through the rounds, and uses pairs of ciphertexts whose plaintexts differ in a chosen way to deduce key bits. It breaks DES reduced to fewer rounds very effectively; against the full sixteen it needs about 2 to the power 47 chosen plaintexts, which is not practical.

munotes.in127

The Strength of DES: Fifty-six Bits, and the Machines That Broke It

The historically interesting part is what it revealed about the S-boxes. DES's S-box design criteria were classified in 1977, which fed twenty years of suspicion that a weakness had been built in. When differential cryptanalysis was published in 1990, the S-boxes turned out to be unusually well chosen against it: small changes to them make DES much weaker. The reasonable conclusion is that the designers knew about the attack and were protecting against it, which is the opposite of what the critics feared.

Linear cryptanalysis, published by Mitsuru Matsui at EUROCRYPT 1993. It finds linear approximations that hold with probability a little away from one half, and accumulates enough of them to deduce key bits. Against the full cipher it needs about 2 to the power 47 known plaintexts, which is a weaker requirement than differential's chosen plaintexts and still far beyond reach.

Timing attacks. Not an attack on the algorithm at all but on an implementation: measuring how long a decryption takes and inferring key bits from the variation. DES is relatively resistant because its operations are table lookups and permutations with little data-dependent branching, but the category matters enormously for the public-key ciphers of the later chapters.

Why the key was ever 56 bits

It is worth being able to answer this, because it is asked as a criticism.

Hardware in 1977. A cipher that had to run at line speed on a single chip had a real budget, and every extra key bit cost registers and wiring.

The block size. DES's 64-bit block and Feistel structure make a 64-bit key natural, and eight of those bits were given to parity, which was standard practice for keys carried over noisy links.

The assessment of the threat. The judgement was that a machine capable of exhaustive search would cost tens of millions of dollars and be available only to a state. That judgement was correct in 1977 and wrong by 1998, and the twenty-one years between are the interval over which computing became cheap. That is the security trend of this book's second chapter, stated as a single cipher's biography.

Distinctions that carry marks

Brute force on DESDifferential cryptanalysisLinear cryptanalysis
Needs2 to the power 55 decryptions on averageabout 2 to the power 47 chosen plaintextsabout 2 to the power 47 known plaintexts
Uses the algorithmnoyesyes
Practical against full DESyes, since 1998nono
Published bynobody; it is arithmeticBiham and Shamir, 1990 to 1993Matsui, 1993
Killed DESyesnono
munotes.in128

The Strength of DES: Fifty-six Bits, and the Machines That Broke It

The key-length criticismThe S-box criticism
Was56 bits is too fewthe classified design criteria hide a weakness
Made byDiffie and Hellman, 1977many, for twenty years
Outcomecorrect, demonstrated 1997 to 1999unfounded: the S-boxes proved strong against differential cryptanalysis

What beginners get wrong here

Quoting 56 hours as DES's strength. That was one machine finding one lucky key. The expected time at that machine's own rate was 113.7 hours.

Saying DES was broken by cryptanalysis. It was broken by exhaustive search. Differential and linear cryptanalysis are real results and have never been practical against the full cipher.

Saying DES is insecure because it is old. It is insecure because 2 to the power 56 is searchable. Give the number and a date.

Thinking the classified S-box criteria hid a back door. The evidence went the other way: the S-boxes are close to optimal against an attack that was not public when they were chosen.

Forgetting the 64-bit block. Even with a longer key, a 64-bit block is a liability in bulk use, which is what finally retired Triple DES. The key is not the only parameter.

Quick revision

  • Two questions: the key length, and whether cryptanalysis beats brute force. DES failed the first and passed the second.
  • Keyspace 2 to the power 56, which is 72,057,594,037,927,936; expected search 2 to the power 55.
  • DES-I 17 June 1997, about 96 days. DES-II-1 completed 23 February 1998, about 41 days. DES-II-2 17 July 1998, the EFF DES Cracker in 56 hours. DES-III 18 to 19 January 1999, under 24 hours.
  • The EFF machine: under $250,000, 88 thousand million keys a second. Its expected time was 113.7 hours; the 56 was luck.
  • Today: one core 1.1 years, a graphics card 0.4 days.
  • Differential cryptanalysis (Biham and Shamir) about 2 to the power 47 chosen plaintexts; linear cryptanalysis (Matsui, 1993) about 2 to the power 47 known plaintexts. Both mean 1,125 terabytes under one key, so neither is practical.
  • The S-boxes proved strong against differential cryptanalysis, which answered twenty years of suspicion about their classified design criteria.
  • DES was withdrawn 19 May 2005.

Test yourself

1. On what two grounds is the strength of a cipher such as DES assessed? On the length of its key, which decides how long an exhaustive search takes; and on the nature of its algorithm, which decides whether any cryptanalytic attack does better than exhaustive search.

2. Give the size of the DES keyspace and the expected number of trials for brute force. 2 to the power 56, which is 72,057,594,037,927,936 keys. Exhaustive search succeeds after half of them on average, so about 2 to the power 55, which is 36,028,797,018,963,968 trials.

munotes.in129

The Strength of DES: Fifty-six Bits, and the Machines That Broke It

3. Describe the EFF DES Cracker and what it demonstrated. A machine built by the Electronic Frontier Foundation in 1998 from an ordinary personal computer driving an array of custom chips, costing less than $250,000 and searching 88 thousand million keys a second. On 17 July 1998 it won RSA's DES-II-2 challenge, finding the key in 56 hours. It demonstrated that a 56-bit key could be searched by a well-funded private organisation rather than only by a state.

4. Why is it misleading to quote 56 hours as the time to break DES? Because that was the time to find one particular key, which happened to lie about a quarter of the way through the keyspace. At the machine's own rate of 88 thousand million keys a second the expected time was 113.7 hours, so about five days is the honest average for that hardware.

5. Why did differential and linear cryptanalysis never break DES in practice? Because both need about 2 to the power 47 plaintexts under a single key, which is over 140 million million blocks, or about 1,125 terabytes of data. No real system has encrypted that much under one DES key, and differential cryptanalysis additionally requires the plaintexts to be chosen by the attacker.

6. What did the publication of differential cryptanalysis reveal about the DES S-boxes? That they were unusually well chosen to resist it. Small modifications to them make DES substantially weaker against the attack. Since the design criteria had been classified in 1977 and the attack was not public until 1990, the reasonable inference is that the designers knew of the attack and defended against it, which contradicted the long-standing suspicion that the secrecy concealed a weakness.

7. Why was DES given a 56-bit key, and when did that judgement fail? Because a cipher designed in 1977 to run on a single chip at line speed had a hardware budget, because eight bits of the natural 64-bit key were used for parity, and because the assessment was that a machine capable of exhaustive search would cost tens of millions and be available only to a state. That judgement held until 1997, when a volunteer effort found a key in about 96 days, and failed decisively in July 1998 when a $250,000 machine did it in 56 hours.

Contents This chapter on its own page

munotes.in130

Chapter Twenty-Four

The Advanced Encryption Standard

Syllabus topic Module 1, "Classical Encryption Techniques: AES (round details not expected)"

In one line

AES is Rijndael with a 128-bit block and a key of 128, 192 or 256 bits, run for 10, 12 or 14 rounds. It is not a Feistel cipher: every round transforms the whole block, and every stage is individually reversible.

In the wording a student can write in an examination: the Advanced Encryption Standard is a symmetric block cipher published by NIST as FIPS PUB 197 on 26 November 2001, and updated on 9 May 2023. It is a member of the Rijndael family, designed by Joan Daemen and Vincent Rijmen, selected in 2000 as the winner of a public competition. The block size is 128 bits and the key may be 128, 192 or 256 bits, giving 10, 12 or 14 rounds respectively. Each round applies four stages to the whole 128-bit state: SubBytes, a byte substitution; ShiftRows, a row-wise permutation; MixColumns, a column-wise mixing; and AddRoundKey, an exclusive-or with the round key. The last round omits MixColumns.

Why there was a competition

DES's S-box criteria were classified, and twenty years of suspicion followed. NIST drew the obvious conclusion and ran the replacement in the open.

The process was announced in 1997, fifteen submissions were accepted, five became finalists (MARS, RC6, Rijndael, Serpent and Twofish), and everybody in the world was invited to attack all of them for several years. Rijndael was selected in 2000, and FIPS 197 published it on 26 November 2001. AES has been under public attack ever since, and no attack materially better than exhaustive search is known against the full cipher.

That process is the real lesson of this chapter, and it is worth an answer of its own: an open competition is evidence, and secrecy is not. It is Kerckhoffs's principle applied to the choice of a standard rather than to the use of one.

The four stages, one paragraph each

The 128-bit block is held as a state: a 4 by 4 array of bytes, filled column by column. All four stages act on that array.

SubBytes. Every byte of the state is replaced independently, through a single 256-entry substitution table. This is the only non-linear stage and it provides the confusion. It is the counterpart of DES's eight S-boxes, except that AES has one box used sixteen times rather than eight different boxes, and unlike DES's it maps eight bits to eight and is therefore invertible.

ShiftRows. Row 0 of the state is left alone. Row 1 is rotated left by one byte, row 2 by two and row 3 by three. It is a pure permutation of bytes and provides diffusion between the columns, which matters because the next stage works within a column and would otherwise never mix them.

munotes.in131

The Advanced Encryption Standard

MixColumns. Each column of four bytes is replaced by a combination of all four, computed in the finite field GF(2 to the power 8). It provides diffusion within a column: change one byte of the input column and every byte of the output column changes. MU's syllabus says the round details are not expected, so the field arithmetic is not walked here; what a student needs is that it is a linear mixing, that it is invertible, and that it is why a one-byte change reaches the whole block in two rounds.

AddRoundKey. The 128 bits of the state are exclusive-ored with 128 bits of round key. It is the only stage that uses the key, and it is deliberately the simplest.

The structure around them: one AddRoundKey before the first round, then the four stages for each round, except that the last round has no MixColumns. That omission is not an oversight; it makes the decryption structure match the encryption structure, and it is a standing examination question.

The cipher, in one file

RCON = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36,
        0x6C, 0xD8, 0xAB, 0x4D]


def xtime(a):
    """Multiply by x in GF(2^8), reducing by the AES polynomial 0x11B."""
    a <<= 1
    return (a ^ 0x1B) & 0xFF if a & 0x100 else a


def gmul(a, b):
    """Multiplication in GF(2^8)."""
    p = 0
    for _ in range(8):
        if b & 1:
            p ^= a
        b >>= 1
        a = xtime(a)
    return p


def _build_sbox():
    """FIPS 197 s.5.1.1: the inverse in GF(2^8), then the affine transformation."""
    inv = [0] * 256
    for i in range(1, 256):
        for j in range(1, 256):
            if gmul(i, j) == 1:
                inv[i] = j
                break
    box = []
    for i in range(256):
        x = inv[i]
        y = x
        for _ in range(4):
            x = ((x << 1) | (x >> 7)) & 0xFF
            y ^= x
        box.append(y ^ 0x63)
    return box


SBOX = _build_sbox()
INV_SBOX = [0] * 256
for _i, _v in enumerate(SBOX):
    INV_SBOX[_v] = _i


def key_expansion(key):
    nk = len(key) // 4
    nr = nk + 6
    w = [list(key[4 * i:4 * i + 4]) for i in range(nk)]
    for i in range(nk, 4 * (nr + 1)):
        t = list(w[i - 1])
        if i % nk == 0:
            t = t[1:] + t[:1]
            t = [SBOX[b] for b in t]
            t[0] ^= RCON[i // nk - 1]
        elif nk > 6 and i % nk == 4:
            t = [SBOX[b] for b in t]
        w.append([w[i - nk][j] ^ t[j] for j in range(4)])
    return w, nr


def _add_round_key(s, w, rnd):
    for c in range(4):
        for r in range(4):
            s[r][c] ^= w[rnd * 4 + c][r]


def _sub_bytes(s, box=None):
    box = box or SBOX
    for r in range(4):
        for c in range(4):
            s[r][c] = box[s[r][c]]


def _shift_rows(s):
    for r in range(1, 4):
        s[r] = s[r][r:] + s[r][:r]


def _inv_shift_rows(s):
    for r in range(1, 4):
        s[r] = s[r][-r:] + s[r][:-r]


def _mix_columns(s):
    for c in range(4):
        a = [s[r][c] for r in range(4)]
        s[0][c] = gmul(a[0], 2) ^ gmul(a[1], 3) ^ a[2] ^ a[3]
        s[1][c] = a[0] ^ gmul(a[1], 2) ^ gmul(a[2], 3) ^ a[3]
        s[2][c] = a[0] ^ a[1] ^ gmul(a[2], 2) ^ gmul(a[3], 3)
        s[3][c] = gmul(a[0], 3) ^ a[1] ^ a[2] ^ gmul(a[3], 2)


def _inv_mix_columns(s):
    for c in range(4):
        a = [s[r][c] for r in range(4)]
        s[0][c] = gmul(a[0], 14) ^ gmul(a[1], 11) ^ gmul(a[2], 13) ^ gmul(a[3], 9)
        s[1][c] = gmul(a[0], 9) ^ gmul(a[1], 14) ^ gmul(a[2], 11) ^ gmul(a[3], 13)
        s[2][c] = gmul(a[0], 13) ^ gmul(a[1], 9) ^ gmul(a[2], 14) ^ gmul(a[3], 11)
        s[3][c] = gmul(a[0], 11) ^ gmul(a[1], 13) ^ gmul(a[2], 9) ^ gmul(a[3], 14)


def _to_state(b):
    return [[b[r + 4 * c] for c in range(4)] for r in range(4)]


def _from_state(s):
    return bytes(s[r][c] for c in range(4) for r in range(4))


def encrypt_block(block, key, trace=None):
    w, nr = key_expansion(key)
    s = _to_state(block)
    _add_round_key(s, w, 0)
    if trace is not None:
        trace.append(('start', _from_state(s).hex()))
    for rnd in range(1, nr):
        _sub_bytes(s)
        _shift_rows(s)
        _mix_columns(s)
        _add_round_key(s, w, rnd)
        if trace is not None:
            trace.append(('round %d' % rnd, _from_state(s).hex()))
    _sub_bytes(s)
    _shift_rows(s)
    _add_round_key(s, w, nr)
    if trace is not None:
        trace.append(('round %d' % nr, _from_state(s).hex()))
    return _from_state(s)


def decrypt_block(block, key):
    w, nr = key_expansion(key)
    s = _to_state(block)
    _add_round_key(s, w, nr)
    for rnd in range(nr - 1, 0, -1):
        _inv_shift_rows(s)
        _sub_bytes(s, INV_SBOX)
        _add_round_key(s, w, rnd)
        _inv_mix_columns(s)
    _inv_shift_rows(s)
    _sub_bytes(s, INV_SBOX)
    _add_round_key(s, w, 0)
    return _from_state(s)
munotes.in132

The Advanced Encryption Standard

Run against the standard's own vectors

import aes

print("FIPS 197 Table 3, the only three configurations the standard allows:")
print("   name      key bits  Nk  block bits  Nb  rounds Nr")
for name, kb, nk, nr in (("AES-128", 128, 4, 10), ("AES-192", 192, 6, 12),
                         ("AES-256", 256, 8, 14)):
    print("   %-9s %8d  %2d  %10d  %2d  %9d" % (name, kb, nk, 128, 4, nr))
print()

print("the S-box is not a magic table: it is the multiplicative inverse in")
print("GF(2 to the power 8) followed by an affine transformation, and this")
print("module BUILDS it that way. FIPS 197 section 5.1.1 gives one example:")
print("   SBOX[0x53] = 0x%02X  (the standard says 0xED)" % aes.SBOX[0x53])
print("   SBOX[0x00] = 0x%02X  (0 has no inverse, so the affine map alone gives 0x63)"
      % aes.SBOX[0x00])
print("   and the inverse table undoes it:",
      all(aes.INV_SBOX[aes.SBOX[i]] == i for i in range(256)))
print()

print("FIPS 197 Appendix B, the worked example:")
key = bytes.fromhex("2b7e151628aed2a6abf7158809cf4f3c")
pt = bytes.fromhex("3243f6a8885a308d313198a2e0370734")
trace = []
ct = aes.encrypt_block(pt, key, trace)
print("   key        :", key.hex())
print("   plaintext  :", pt.hex())
print("   ciphertext :", ct.hex())
print("   the standard's answer is 3925841d02dc09fbdc118597196a0b32")
print("   they agree :", ct.hex() == "3925841d02dc09fbdc118597196a0b32")
print()
print("   the state after each round:")
for label, state in trace:
    print("     %-9s %s" % (label, state))
print()

print("FIPS 197 Appendix C, all three key sizes on one block:")
pt2 = bytes.fromhex("00112233445566778899aabbccddeeff")
for kh, want in (("000102030405060708090a0b0c0d0e0f", "69c4e0d86a7b0430d8cdb78070b4c55a"),
                 ("000102030405060708090a0b0c0d0e0f1011121314151617",
                  "dda97ca4864cdfe06eaf70a0ec0d7191"),
                 ("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
                  "8ea2b7ca516745bfeafc49904b496089")):
    got = aes.encrypt_block(pt2, bytes.fromhex(kh)).hex()
    print("   AES-%-3d  %s  matches the standard: %s"
          % (len(kh) * 4, got, got == want))
print()
print("and decryption returns the plaintext:",
      aes.decrypt_block(aes.encrypt_block(pt2, bytes.fromhex("000102030405060708090a0b0c0d0e0f")),
                        bytes.fromhex("000102030405060708090a0b0c0d0e0f")) == pt2)
munotes.in133

The Advanced Encryption Standard

FIPS 197 Table 3, the only three configurations the standard allows:
   name      key bits  Nk  block bits  Nb  rounds Nr
   AES-128        128   4         128   4         10
   AES-192        192   6         128   4         12
   AES-256        256   8         128   4         14

the S-box is not a magic table: it is the multiplicative inverse in
GF(2 to the power 8) followed by an affine transformation, and this
module BUILDS it that way. FIPS 197 section 5.1.1 gives one example:
   SBOX[0x53] = 0xED  (the standard says 0xED)
   SBOX[0x00] = 0x63  (0 has no inverse, so the affine map alone gives 0x63)
   and the inverse table undoes it: True

FIPS 197 Appendix B, the worked example:
   key        : 2b7e151628aed2a6abf7158809cf4f3c
   plaintext  : 3243f6a8885a308d313198a2e0370734
   ciphertext : 3925841d02dc09fbdc118597196a0b32
   the standard's answer is 3925841d02dc09fbdc118597196a0b32
   they agree : True

   the state after each round:
     start     193de3bea0f4e22b9ac68d2ae9f84808
     round 1   a49c7ff2689f352b6b5bea43026a5049
     round 2   aa8f5f0361dde3ef82d24ad26832469a
     round 3   486c4eee671d9d0d4de3b138d65f58e7
     round 4   e0927fe8c86363c0d9b1355085b8be01
     round 5   f1006f55c1924cef7cc88b325db5d50c
     round 6   260e2e173d41b77de86472a9fdd28b25
     round 7   5a4142b11949dc1fa3e019657a8c040c
     round 8   ea835cf00445332d655d98ad8596b0c5
     round 9   eb40f21e592e38848ba113e71bc342d2
     round 10  3925841d02dc09fbdc118597196a0b32

FIPS 197 Appendix C, all three key sizes on one block:
   AES-128  69c4e0d86a7b0430d8cdb78070b4c55a  matches the standard: True
   AES-192  dda97ca4864cdfe06eaf70a0ec0d7191  matches the standard: True
   AES-256  8ea2b7ca516745bfeafc49904b496089  matches the standard: True

and decryption returns the plaintext: True

Read four things out of that run.

The S-box is built, and the standard's example comes out right. FIPS 197 section 5.1.1 gives 0x53 mapping to 0xED, and the generated table agrees. Since the table was computed from the definition rather than copied, that agreement is evidence that the definition in section 4 above is correct. The chapter can therefore explain where the S-box comes from instead of asking the reader to accept it.

SBOX[0x00] is 0x63. Zero has no multiplicative inverse, so the construction defines its inverse as zero and the affine transformation alone produces 0x63. It is the one special case and it is worth knowing, because a reader who tries to reproduce the table will hit it first.

The state after every round matches FIPS 197 Appendix B. The standard prints exactly this sequence for exactly this key and plaintext, starting 193de3bea0f4e22b9ac68d2ae9f84808 after the initial AddRoundKey and ending at the ciphertext. Any reader with the standard open can check this chapter line by line.

munotes.in134

The Advanced Encryption Standard

All three key sizes are correct, and decryption returns the plaintext. Appendix C of the standard gives the three ciphertexts for one block under 128, 192 and 256-bit keys, and all three match.

AES against DES

DESAES
Published1977, FIPS 462001, FIPS 197
Block size64 bits128 bits
Key sizes56 bits128, 192 or 256 bits
Rounds1610, 12 or 14
StructureFeistel, half the block per roundsubstitution-permutation network, the whole block per round
Round function invertiblenot requiredrequired, and it is
Decryptionsame algorithm, subkeys reverseda different algorithm, the inverse stages
Non-linear parteight S-boxes, 6 bits to 4one S-box, 8 bits to 8
Design criteriaclassifiedpublic, and competitively reviewed
Statuswithdrawn 19 May 2005current, and acceptable at all three key sizes

That table is the most likely question in this part of the syllabus, and the two rows that carry the most marks are the structure row and the design-criteria row.

A worked example: how fast does a change spread

The question. In DES, the avalanche chapter measured that one plaintext bit reached about half the block by round 3. How does AES compare?

Step 1: one byte changes. Say byte 0 of the state changes.

Step 2: SubBytes. Still one byte, now a different value. The substitution is byte by byte, so nothing spreads.

Step 3: ShiftRows. Still one byte, now in a different column.

Step 4: MixColumns. Every byte of that column now depends on the changed byte, so four bytes differ.

Step 5: the second round's ShiftRows. Those four bytes are in one column, and ShiftRows moves them into four different columns, one in each.

Step 6: the second round's MixColumns. Each of the four columns now has a changed byte, so every byte of all four columns changes. All sixteen bytes of the state differ.

So AES reaches full diffusion in two rounds. DES took three to reach half the block and more to reach all of it. That is what operating on the whole block each round buys, and it is why AES needs ten rounds where DES needed sixteen.

What beginners get wrong here

Calling AES a Feistel cipher. It is not. Every round transforms the whole state, and decryption uses inverse stages rather than reversed subkeys.

Saying AES has a 256-bit block. The block is always 128 bits. Only the key varies. Rijndael as designed supports other block sizes; FIPS 197 standardises only the 128-bit block, and says so.

Forgetting that the last round has no MixColumns. It is asked, and the reason is that it makes the inverse cipher's structure match the cipher's.

munotes.in135

The Advanced Encryption Standard

Walking the MixColumns arithmetic when the syllabus says not to. MU prints "round details not expected". Name the stage, say what it achieves, and spend the time on the topics that are examined.

Saying AES is unbreakable. Say that no attack better than exhaustive search is known against the full cipher, and that NIST lists all three key sizes as acceptable. That is a claim with a date and a source behind it.

Quick revision

  • AES: FIPS 197, published 26 November 2001, updated 9 May 2023. Rijndael, by Daemen and Rijmen, selected in 2000 after a public competition; the other four finalists were MARS, RC6, Serpent and Twofish.
  • Block 128 bits always. Key 128, 192 or 256; rounds 10, 12, 14.
  • Not a Feistel cipher: a substitution-permutation network on the whole block, with every stage invertible.
  • Four stages: SubBytes (the only non-linear one, confusion), ShiftRows (diffusion across columns), MixColumns (diffusion within a column, in GF(2 to the power 8)), AddRoundKey (the only use of the key).
  • One AddRoundKey before the first round; the last round has no MixColumns, so that the inverse cipher has the same shape.
  • The S-box is the multiplicative inverse in GF(2 to the power 8) followed by an affine map; 0x53 gives 0xED, and 0x00 gives 0x63 because 0 has no inverse.
  • Full diffusion in two rounds, against DES's three to reach half the block.
  • The design was public and competitively reviewed, which is the answer to DES's classified S-box criteria.

Test yourself

1. Give AES's block size, key sizes and round counts, and name its structure. The block is always 128 bits. The key may be 128, 192 or 256 bits, giving 10, 12 or 14 rounds respectively. The structure is a substitution-permutation network, not a Feistel network: each round transforms the whole 128-bit state.

2. Name the four stages of an AES round and say what each contributes. SubBytes substitutes each byte through a single table and is the only non-linear stage, providing confusion. ShiftRows rotates rows 1, 2 and 3 left by one, two and three bytes, providing diffusion across columns. MixColumns replaces each column by a combination of its four bytes in GF(2 to the power 8), providing diffusion within a column. AddRoundKey exclusive-ors the state with the round key and is the only stage that uses the key.

3. Why does the last round omit MixColumns? So that the inverse cipher has the same structure as the cipher. With the omission, decryption can be expressed as the same sequence of inverse stages in the same arrangement, which makes implementations simpler and lets encryption and decryption share code paths.

munotes.in136

The Advanced Encryption Standard

4. How is the AES S-box constructed, and what does 0x53 map to? Each byte is replaced by its multiplicative inverse in the finite field GF(2 to the power 8), with zero mapping to zero, and the result is put through an affine transformation over GF(2). FIPS 197's own example is that 0x53 maps to 0xED, which this chapter's generated table reproduces.

5. Give three differences between DES and AES. DES has a 64-bit block and a 56-bit key; AES has a 128-bit block and a key of 128, 192 or 256 bits. DES is a Feistel cipher whose round function need not be invertible and whose decryption is the same algorithm with reversed subkeys; AES is a substitution-permutation network whose stages are all invertible and whose decryption uses the inverse stages. DES's S-box design criteria were classified; AES was chosen by open competition with public analysis.

6. How many rounds does a single-byte change take to affect the whole AES state, and why? Two. In the first round MixColumns spreads the change across the four bytes of its column; the next ShiftRows moves those four bytes into four different columns, and that round's MixColumns then changes every byte of all four columns, which is the whole state.

7. What can honestly be said about AES's security? That no attack materially better than exhaustive search is known against the full cipher, after public analysis since 1998, and that NIST's transition guidance lists AES-128, AES-192 and AES-256 as acceptable for encryption and decryption. Exhaustive search of a 128-bit key is beyond any foreseeable machine.

Contents This chapter on its own page

munotes.in137

Chapter Twenty-Five

Multiple Encryption, Double DES and the Meet-in-the-Middle Attack

Syllabus topic Module 1, "Classical Encryption Techniques: Multiple Encryption and Triple DES"

In one line

Encrypting twice with two keys does not double the key length; it adds one bit. The meet-in-the-middle attack costs about two searches of a single keyspace rather than one search of the combined one, which is why nobody uses double DES and everybody used triple DES.

In the wording a student can write in an examination: given a block cipher with an n-bit key, double encryption computes C = E(K2, E(K1, P)) with two independent keys, giving 2n bits of key material. It appears to raise the work of exhaustive search from 2 to the power n to 2 to the power 2n. It does not, because of the meet-in-the-middle attack: given one known plaintext and ciphertext pair, the attacker encrypts the plaintext under every possible K1 and stores the results, then decrypts the ciphertext under every possible K2 and looks each result up in that store. Any match gives a candidate pair. The work is about 2 to the power n + 1 encryptions and 2 to the power n storage, so double encryption gains only one bit of effective key length.

Why anybody tried it

DES's 56-bit key was short and the algorithm itself was sound, so the obvious repair was to keep the algorithm and use it more than once with more key. It is a general idea and it is worth knowing the general answer, because the same question arises about any cipher.

Two separate hopes were involved and both have to be checked.

Hope 1: two passes of the cipher are not equivalent to one pass. If for every pair K1, K2 there were a single key K3 with E(K2, E(K1, P)) equal to E(K3, P), double encryption would be pointless: the attacker would simply search the 2 to the power n single keys. This property is called being a group, and it was shown in 1992 that DES is not a group, so double DES really is a different cipher from DES. So far so good.

Hope 2: the effective key length is 2n. This is the one that fails.

The meet-in-the-middle attack

The idea is due to Diffie and Hellman, 1977, and it is the earliest and clearest example of trading memory for time.

Write the double encryption as two halves with a middle value X between them.

X = E(K1, P)

C = E(K2, X)

X = D(K2, C)

So X can be reached from the left by encrypting P, and from the right by decrypting C. The attacker does not know X, but knows that the two ways of reaching it must agree.

Step 1. For every possible K1, compute E(K1, P) and store the key against the result. That is 2 to the power n encryptions and a table of 2 to the power n entries.

munotes.in138

Multiple Encryption, Double DES and the Meet-in-the-Middle Attack

Step 2. For every possible K2, compute D(K2, C) and look the result up in the table. Every hit is a pair that maps P to C. That is another 2 to the power n operations.

Step 3. Most hits are wrong, so filter them with further known plaintext and ciphertext pairs until one pair survives.

The cost. About 2 to the power n + 1 cipher operations and 2 to the power n storage, against 2 to the power 2n for brute force on the pair. For DES that is about 2 to the power 57 rather than 2 to the power 112.

The attack, performed

The listing uses a toy block cipher with a 16-bit block and a 16-bit key, so that 2 to the power 16 is 65,536 and the whole attack runs in seconds. The arithmetic is exactly the arithmetic of the attack on double DES; only the exponents are smaller.

# The meet-in-the-middle attack, performed. A toy block cipher with a 16-bit key
# stands in for DES, so the attack finishes in seconds and the arithmetic is the
# same arithmetic.

import itertools

MASK = 0xFFFF

def E(block, key):
    """A toy 16-bit block cipher. Four rounds of add, rotate, xor, substitute."""
    x = block
    for r in range(4):
        x = (x + key + r) & MASK
        x = ((x << 5) | (x >> 11)) & MASK
        x = x ^ ((key >> 3) | ((key << 13) & MASK))
        x = ((x * 0x2545) + 0x9E37) & MASK
    return x

def D(block, key):
    """Its inverse, needed for the attack and to check E is a bijection."""
    x = block
    for r in reversed(range(4)):
        x = ((x - 0x9E37) * pow(0x2545, -1, 0x10000)) & MASK
        x = x ^ ((key >> 3) | ((key << 13) & MASK))
        x = ((x >> 5) | (x << 11)) & MASK
        x = (x - key - r) & MASK
    return x

# A cipher that is not a bijection is not a cipher. Check it before using it.
print("is E a bijection for key 0x1234?",
      len({E(b, 0x1234) for b in range(65536)}) == 65536)
print("does D undo E?", all(D(E(b, 0x1234), 0x1234) == b for b in range(0, 65536, 997)))
print()

K1, K2 = 0x2B7E, 0x1528
P = 0x3243
C = E(E(P, K1), K2)
print("double encryption with two 16-bit keys, so 32 bits of key in all")
print("   plaintext  = %04X" % P)
print("   K1         = %04X   K2 = %04X" % (K1, K2))
print("   ciphertext = %04X" % C)
print()

print("brute force on the pair would be 2 to the power 32 =",
      format(2 ** 32, ","), "trials")
print("the meet-in-the-middle attack is 2 to the power 16 twice =",
      format(2 ** 17, ","))
print()

# Step 1: encrypt P under every possible first key, and remember the middles.
table = {}
for k in range(65536):
    table.setdefault(E(P, k), []).append(k)
print("step 1: encrypted the plaintext under all", format(65536, ","),
      "first keys, and stored the middles")
print("        distinct middle values:", format(len(table), ","))

# Step 2: decrypt C under every possible second key and look the middle up.
found = []
for k2 in range(65536):
    m = D(C, k2)
    for k1 in table.get(m, ()):
        found.append((k1, k2))
print("step 2: decrypted the ciphertext under all", format(65536, ","),
      "second keys and looked each middle up")
print("        candidate key pairs found:", format(len(found), ","))
print("        the true pair is among them:", (K1, K2) in found)
print()

# Step 3: more known pairs, until one candidate is left.
extra = [0x1A2B, 0x7799, 0xC0DE]
survivors = found
for i, p2 in enumerate(extra, 1):
    c2 = E(E(p2, K1), K2)
    survivors = [(a, b) for a, b in survivors if E(E(p2, a), b) == c2]
    print("step 3.%d: a further known pair (%04X to %04X) leaves %s candidate(s)"
          % (i, p2, c2, format(len(survivors), ",")))
print()
print("the recovered key pair:", " ".join("%04X" % k for k in survivors[0]))
print("which is the real one  :", survivors[0] == (K1, K2))
print()
print("why more than one pair was needed: the key is 32 bits and a block is 16,")
print("so one pair leaves about 2 to the power 16 wrong pairs that happen to fit.")
print("Each further pair divides the survivors by about 2 to the power 16.")
print()
print("so double encryption with two n-bit keys costs the attacker about 2 to the")
print("power n + 1, not 2 to the power 2n. The second key bought ONE bit.")
munotes.in139

Multiple Encryption, Double DES and the Meet-in-the-Middle Attack

is E a bijection for key 0x1234? True
does D undo E? True

double encryption with two 16-bit keys, so 32 bits of key in all
   plaintext  = 3243
   K1         = 2B7E   K2 = 1528
   ciphertext = CCC4

brute force on the pair would be 2 to the power 32 = 4,294,967,296 trials
the meet-in-the-middle attack is 2 to the power 16 twice = 131,072

step 1: encrypted the plaintext under all 65,536 first keys, and stored the middles
        distinct middle values: 40,261
step 2: decrypted the ciphertext under all 65,536 second keys and looked each middle up
        candidate key pairs found: 65,447
        the true pair is among them: True

step 3.1: a further known pair (1A2B to E72A) leaves 5 candidate(s)
step 3.2: a further known pair (7799 to E986) leaves 1 candidate(s)
step 3.3: a further known pair (C0DE to 8AB4) leaves 1 candidate(s)

the recovered key pair: 2B7E 1528
which is the real one  : True

why more than one pair was needed: the key is 32 bits and a block is 16,
so one pair leaves about 2 to the power 16 wrong pairs that happen to fit.
Each further pair divides the survivors by about 2 to the power 16.

so double encryption with two n-bit keys costs the attacker about 2 to the
power n + 1, not 2 to the power 2n. The second key bought ONE bit.
munotes.in140

Multiple Encryption, Double DES and the Meet-in-the-Middle Attack

Read six things out of that run.

The toy cipher was checked first. It is a bijection over all 65,536 blocks and its inverse undoes it. A demonstration of a cryptographic attack built on a function that is not a cipher would be worthless, and checking it costs one line.

Brute force on the pair would be 4,294,967,296 trials; the attack is 131,072. A factor of about 32,768, which is 2 to the power 15, and in general the saving is 2 to the power n minus 1.

Step 1 produced 40,261 distinct middle values from 65,536 keys. Not 65,536: several keys happen to send this plaintext to the same middle value. That is the birthday phenomenon and it is why the table stores a list of keys per middle rather than one key.

One known pair left 65,447 candidate pairs. This is the number every description of the attack omits. There are 2 to the power 32 key pairs and only 2 to the power 16 possible ciphertexts, so about 2 to the power 16 wrong pairs map this plaintext to this ciphertext by accident. The attack does not identify the key from one pair; it narrows it.

Two pairs left five, and three left one. Each further known pair divides the survivors by about 2 to the power 16, because it imposes 16 more bits of constraint. The number of pairs needed is about the key size divided by the block size, which for double DES is 112 over 64, so two pairs are usually enough and three are certain. That is the honest general statement.

The recovered key was 2B7E 1528, the true one. Not a candidate, not a near miss: the real key pair, found from three known plaintexts.

What this means for DES

Double DES has 112 bits of key material and about 57 bits of effective strength against an attacker willing to store 2 to the power 56 entries. That storage is enormous, which is the one honest defence of double DES: the attack is memory-bound rather than time-bound, and 2 to the power 56 eight-byte entries is about 576 petabytes.

munotes.in141

Multiple Encryption, Double DES and the Meet-in-the-Middle Attack

But a security argument that rests on an attacker's storage budget is a weak argument, and it gets weaker every year. So the answer was never double encryption. It was triple encryption, and the next chapter is why three passes escape this attack where two do not.

The short version, which is worth having in advance: against triple encryption the same trick leaves the attacker with about 2 to the power 2n work rather than 2 to the power n + 1, because one of the three keys has to be guessed before the meet-in-the-middle can begin. For DES that is 2 to the power 112, which is beyond reach.

A worked example: counting the cost for DES

The setting. Double DES, K1 and K2 each 56 bits, one known plaintext and ciphertext pair.

Step 1: the table. 2 to the power 56 encryptions, each storing a 64-bit middle and a 56-bit key, so about 15 bytes an entry. 2 to the power 56 times 15 bytes is about 1,080 petabytes. That is the real obstacle, and it is storage, not time.

Step 2: the matching. Another 2 to the power 56 decryptions, each with one lookup.

Step 3: the total. About 2 to the power 57 cipher operations. From the strength-of-DES chapter, a graphics card does 2 to the power 55 in 0.4 days, so 2 to the power 57 is about 1.6 days of computing on hardware anybody can buy, plus a thousand petabytes of storage nobody has at home.

Step 4: the filtering. With a 112-bit key and a 64-bit block, one pair leaves about 2 to the power 48 candidates and two pairs leave about one, so two known pairs suffice.

The step that carries the marks. Step 3. Double DES is not broken in practice today because of the storage, and it is not used because a cipher whose security rests on the attacker's disk budget is not a cipher anybody should choose. Say both halves.

Distinctions that carry marks

Single DESDouble DESTriple DES
Key material56 bits112 bits112 or 168 bits
Apparent strength2 to the power 562 to the power 1122 to the power 112 or 168
Actual strength2 to the power 55about 2 to the power 57about 2 to the power 112
Attackexhaustive searchmeet-in-the-middlemeet-in-the-middle with one key guessed
Storage the attack needsnoneabout 2 to the power 56 entriesabout 2 to the power 56 entries
Used in practiceuntil 1998neverwidely, until 2024
Brute forceMeet-in-the-middle
Time2 to the power 2nabout 2 to the power n + 1
Storagenegligible2 to the power n
Known pairs neededone usually sufficesabout key size divided by block size
Tradesnothingmemory for time
munotes.in142

Multiple Encryption, Double DES and the Meet-in-the-Middle Attack

What beginners get wrong here

Saying double DES gives 112 bits of security. It gives 112 bits of key material and about 57 bits of strength. The distinction is the whole topic.

Forgetting the storage. The attack needs 2 to the power n entries stored, and for DES that is hundreds of petabytes. An answer that quotes only the time has given half the cost.

Thinking one known pair identifies the key. The run shows 65,447 candidates from one pair. Several pairs are needed, and the number is about the key size divided by the block size.

Confusing "DES is not a group" with "double DES is secure". Not being a group is what makes double DES a genuinely different cipher rather than a single DES in disguise. It says nothing about the meet-in-the-middle attack, which works anyway.

Attributing the attack to the wrong people. It is Diffie and Hellman, 1977, the same pair as the key exchange of a later chapter.

Quick revision

  • Double encryption: C = E(K2, E(K1, P)). 2n bits of key material, about n + 1 bits of strength.
  • DES is not a group (shown 1992), so double DES is a different cipher from DES; that is necessary and not sufficient.
  • Meet-in-the-middle (Diffie and Hellman, 1977): encrypt P under every K1 and store the middles; decrypt C under every K2 and look each middle up; every match is a candidate.
  • Cost: about 2 to the power n + 1 operations and 2 to the power n storage, against 2 to the power 2n for brute force.
  • For double DES: about 2 to the power 57 operations, and roughly a thousand petabytes of storage.
  • One known pair is not enough. In the run it left 65,447 candidates; two left five; three left one. The number needed is about the key size divided by the block size.
  • The attack trades memory for time, and it is the earliest clear example of that trade in cryptography.
  • Triple encryption escapes it because one key must be guessed first, raising the work to about 2 to the power 2n.

Test yourself

1. Write the double encryption formula and say how much key material it uses. C = E(K2, E(K1, P)), with decryption P = D(K1, D(K2, C)). With two independent n-bit keys it uses 2n bits of key material, which for DES is 112 bits.

2. Describe the meet-in-the-middle attack. Given a known plaintext and ciphertext pair, note that the middle value X satisfies both X = E(K1, P) and X = D(K2, C). Encrypt P under every possible K1 and store each result with its key; then decrypt C under every possible K2 and look each result up in the table. Every match is a candidate key pair. Further known pairs filter the candidates until one survives.

munotes.in143

Multiple Encryption, Double DES and the Meet-in-the-Middle Attack

3. What does the attack cost, and what is the effective strength of double DES? About 2 to the power n + 1 cipher operations and 2 to the power n storage, so for DES about 2 to the power 57 operations and 2 to the power 56 stored entries. The effective strength of double DES is therefore about 57 bits rather than the 112 its key material suggests: the second key buys one bit.

4. Why is one known plaintext and ciphertext pair not enough? Because there are far more key pairs than ciphertexts. With a 112-bit key and a 64-bit block there are 2 to the power 112 pairs and only 2 to the power 64 ciphertexts, so about 2 to the power 48 wrong pairs map that plaintext to that ciphertext by accident. In the chapter's 16-bit demonstration one pair left 65,447 candidates, two left five and three left one.

5. What does it mean to say DES is not a group, and why does it matter? That there is generally no single key K3 for which E(K3, P) equals E(K2, E(K1, P)). It matters because if DES were a group, double encryption would be equivalent to single encryption under some other key and would add nothing at all; not being a group is what makes double DES a genuinely larger family of mappings. It is necessary for double encryption to be worth anything, but not sufficient, since the meet-in-the-middle attack applies regardless.

6. What does the meet-in-the-middle attack trade, and why is that significant? It trades memory for time: it replaces 2 to the power 2n work with about 2 to the power n + 1 work plus 2 to the power n storage. It is significant because the only practical obstacle to breaking double DES is that storage, and a security argument resting on an attacker's disk budget weakens every year, which is why triple rather than double encryption was adopted.

7. Why does triple encryption resist the attack? Because the attacker cannot reach the middle from both ends in one pass: one of the three keys must be guessed before the meet-in-the-middle can be set up, and that multiplies the work by the size of that key's space. For three-key triple DES the work rises to about 2 to the power 112, which is beyond any foreseeable machine.

Contents This chapter on its own page

munotes.in144

Chapter Twenty-Six

Triple DES, and Where It Stands Now

Syllabus topic Module 1, "Classical Encryption Techniques: Multiple Encryption and Triple DES"

In one line

Run DES three times, encrypt then decrypt then encrypt, with two or three keys. It escapes the meet-in-the-middle attack, it is four times slower than DES, and as of 1 January 2024 it is no longer an approved block cipher.

In the wording a student can write in an examination: Triple DES, also written 3DES and called the Triple Data Encryption Algorithm or TDEA, applies the DES algorithm three times in the sequence encrypt, decrypt, encrypt:

C = E(K3, D(K2, E(K1, P)))

P = D(K1, E(K2, D(K3, C)))

With three independent keys it uses 168 bits of key material and has an effective strength of about 112 bits. With K3 set equal to K1, called two-key triple DES, it uses 112 bits. Setting all three keys equal reduces it exactly to single DES, which is what made it deployable on equipment that already spoke DES.

Why three passes and not two

The previous chapter settled it. Two passes give about n + 1 bits of strength because the attacker can meet in the middle from both ends.

With three passes the attacker cannot. To set up a meet-in-the-middle they need a value reachable from both ends, and with three stages any such value requires knowing one of the three keys already. So the attack becomes: guess K1, then meet in the middle on the remaining two stages. That costs 2 to the power 56 guesses times the 2 to the power 56 work of the inner attack, which is 2 to the power 112, and for three-key 3DES that is the effective strength. It is beyond any foreseeable machine, and it is why 3DES was the right answer in 1999 and stayed the answer for twenty-five years.

Note what that says about three-key 3DES: 168 bits of key material, about 112 bits of strength. The third key buys almost nothing against this particular attack; it buys protection against a different known-plaintext attack on the two-key variant, which is why the standards kept it.

Why encrypt, decrypt, encrypt

This is the question every paper asks, and the answer has two parts.

Part 1: backward compatibility, which is the real reason. Set K1, K2 and K3 all equal. Then the second stage undoes the first, and what remains is a single encryption under that key. So a device implementing 3DES can talk to a device implementing DES simply by loading the same key three times. If the middle stage were another encryption, three passes with one key would be triple encryption under one key, which no DES device can produce.

Part 2: it is not for security. Decryption and encryption are the same strength in DES, so EDE is no stronger or weaker than EEE. Students sometimes answer that the decryption "adds confusion"; it does not. The reason is interoperability and nothing else.

munotes.in145

Triple DES, and Where It Stands Now

The cipher, and both claims proved

IP = [58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4,
      62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8,
      57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
      61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7]

FP = [40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31,
      38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29,
      36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27,
      34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25]

E = [32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13,
     12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21,
     22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1]

P = [16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10,
     2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25]

PC1 = [57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18,
       10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36,
       63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22,
       14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4]

PC2 = [14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10,
       23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2,
       41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48,
       44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32]

SHIFTS = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]

S = [
 [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7],
  [0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8],
  [4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0],
  [15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]],
 [[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10],
  [3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5],
  [0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15],
  [13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]],
 [[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8],
  [13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1],
  [13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7],
  [1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]],
 [[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15],
  [13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9],
  [10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4],
  [3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]],
 [[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9],
  [14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6],
  [4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14],
  [11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]],
 [[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11],
  [10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8],
  [9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6],
  [4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]],
 [[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1],
  [13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6],
  [1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2],
  [6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]],
 [[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7],
  [1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2],
  [7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8],
  [2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]],
]

WEAK_KEYS = ['0101010101010101', 'FEFEFEFEFEFEFEFE',
             'E0E0E0E0F1F1F1F1', '1F1F1F1F0E0E0E0E']


def bits(data):
    """Bytes to a list of bits, most significant first."""
    return [(b >> (7 - i)) & 1 for b in data for i in range(8)]


def unbits(bl):
    out = bytearray()
    for i in range(0, len(bl), 8):
        v = 0
        for b in bl[i:i + 8]:
            v = (v << 1) | b
        out.append(v)
    return bytes(out)


def permute(bl, table):
    return [bl[i - 1] for i in table]


def left_shift(bl, n):
    return bl[n:] + bl[:n]


def xor(a, b):
    return [x ^ y for x, y in zip(a, b)]


def key_schedule(key):
    """The sixteen 48-bit subkeys, and the C and D halves at every round."""
    kb = permute(bits(key), PC1)
    c, d = kb[:28], kb[28:]
    subkeys, trace = [], []
    for r in range(16):
        c = left_shift(c, SHIFTS[r])
        d = left_shift(d, SHIFTS[r])
        k = permute(c + d, PC2)
        subkeys.append(k)
        trace.append((r + 1, SHIFTS[r], list(c), list(d), k))
    return subkeys, trace


def f(right, subkey):
    """The DES round function: expand, XOR the key, eight S-boxes, permute."""
    x = xor(permute(right, E), subkey)
    out = []
    for i in range(8):
        block = x[i * 6:i * 6 + 6]
        row = block[0] * 2 + block[5]
        col = (block[1] * 8 + block[2] * 4 + block[3] * 2 + block[4])
        v = S[i][row][col]
        out += [(v >> 3) & 1, (v >> 2) & 1, (v >> 1) & 1, v & 1]
    return permute(out, P)


def _rounds(block, subkeys):
    b = permute(bits(block), IP)
    left, right = b[:32], b[32:]
    trace = [(0, list(left), list(right))]
    for r in range(16):
        left, right = right, xor(left, f(right, subkeys[r]))
        trace.append((r + 1, list(left), list(right)))
    return unbits(permute(right + left, FP)), trace


def encrypt_block(block, key):
    subkeys, _ = key_schedule(key)
    return _rounds(block, subkeys)[0]


def decrypt_block(block, key):
    subkeys, _ = key_schedule(key)
    return _rounds(block, subkeys[::-1])[0]


def encrypt_block_trace(block, key):
    subkeys, ktrace = key_schedule(key)
    out, trace = _rounds(block, subkeys)
    return out, trace, ktrace, subkeys


def parity_ok(key):
    """Every byte of a DES key carries odd parity in its low bit."""
    return all(bin(b).count('1') % 2 == 1 for b in key)


def set_parity(key):
    out = bytearray()
    for b in key:
        b &= 0xFE
        out.append(b if bin(b).count('1') % 2 == 1 else b | 1)
    return bytes(out)


# ------------------------------------------------------------- Triple DES
def tdea_encrypt_block(block, k1, k2, k3):
    """Encrypt, Decrypt, Encrypt: the order SP 800-67 prints."""
    return encrypt_block(decrypt_block(encrypt_block(block, k1), k2), k3)


def tdea_decrypt_block(block, k1, k2, k3):
    return decrypt_block(encrypt_block(decrypt_block(block, k3), k2), k1)
munotes.in146

Triple DES, and Where It Stands Now

import des

K1 = bytes.fromhex("0123456789ABCDEF")
K2 = bytes.fromhex("23456789ABCDEF01")
K3 = bytes.fromhex("456789ABCDEF0123")
P = bytes.fromhex("4E6F772069732074")

print("three-key triple DES, in the order the standard prints: E then D then E")
c = des.tdea_encrypt_block(P, K1, K2, K3)
print("   plaintext  :", P.hex().upper())
print("   ciphertext :", c.hex().upper())
print("   decrypted  :", des.tdea_decrypt_block(c, K1, K2, K3).hex().upper())
print()

print("two-key triple DES is the same thing with K3 set equal to K1:")
c2 = des.tdea_encrypt_block(P, K1, K2, K1)
print("   ciphertext :", c2.hex().upper())
print("   decrypted  :", des.tdea_decrypt_block(c2, K1, K2, K1).hex().upper())
print()

print("why the middle stage is a DECRYPTION: with all three keys equal,")
print("triple DES collapses to single DES, so old equipment interoperates.")
same = des.tdea_encrypt_block(P, K1, K1, K1)
single = des.encrypt_block(P, K1)
print("   3DES with K1 = K2 = K3 :", same.hex().upper())
print("   single DES with K1     :", single.hex().upper())
print("   identical              :", same == single)
print()

print("had the middle stage been an encryption, that would not hold:")
eee = des.encrypt_block(des.encrypt_block(des.encrypt_block(P, K1), K1), K1)
print("   E(E(E(P))) with one key:", eee.hex().upper())
print("   equals single DES      :", eee == single)
print()

print("the block limit, from SP 800-67 Rev. 2: one key bundle may protect no")
print("more than 2 to the power 20 blocks of 64 bits.")
blocks = 2 ** 20
print("   blocks                 :", format(blocks, ","))
print("   bytes                  :", format(blocks * 8, ","))
print("   which is               : %.1f mebibytes" % (blocks * 8 / 1024 / 1024))
print()
for name, bits in (("two-key 3DES", 112), ("three-key 3DES", 168),
                   ("effective, three-key", 112), ("AES-128", 128)):
    print("   %-22s %3d bits of key" % (name, bits))
munotes.in147

Triple DES, and Where It Stands Now

three-key triple DES, in the order the standard prints: E then D then E
   plaintext  : 4E6F772069732074
   ciphertext : 314F8327FA7A09A8
   decrypted  : 4E6F772069732074

two-key triple DES is the same thing with K3 set equal to K1:
   ciphertext : B7835779EE26ACB7
   decrypted  : 4E6F772069732074

why the middle stage is a DECRYPTION: with all three keys equal,
triple DES collapses to single DES, so old equipment interoperates.
   3DES with K1 = K2 = K3 : 3FA40E8A984D4815
   single DES with K1     : 3FA40E8A984D4815
   identical              : True

had the middle stage been an encryption, that would not hold:
   E(E(E(P))) with one key: 3B8FAD2B39E386BC
   equals single DES      : False

the block limit, from SP 800-67 Rev. 2: one key bundle may protect no
more than 2 to the power 20 blocks of 64 bits.
   blocks                 : 1,048,576
   bytes                  : 8,388,608
   which is               : 8.0 mebibytes

   two-key 3DES           112 bits of key
   three-key 3DES         168 bits of key
   effective, three-key   112 bits of key
   AES-128                128 bits of key
munotes.in148

Triple DES, and Where It Stands Now

Read five things out of that run.

Three-key and two-key 3DES give different ciphertexts, 314F8327FA7A09A8 and B7835779EE26ACB7, which is a reminder that the two-key variant is a distinct cipher and not a shortcut.

With all three keys equal, 3DES gives exactly single DES. 3FA40E8A984D4815, which is the known answer for this block and key from the DES chapters. That is the backward-compatibility property, demonstrated.

With an EEE arrangement it would not hold. E(E(E(P))) under one key gives 3B8FAD2B39E386BC, which is not single DES. So the choice of EDE is doing real work, and the counterfactual proves it rather than the claim being taken on trust.

munotes.in149

Triple DES, and Where It Stands Now

The block limit is 8,388,608 bytes. 2 to the power 20 blocks of eight bytes, which the program prints as 8.0 mebibytes. This is SP 800-67 Rev. 2's own restriction on a key bundle, and it is the most practically important fact about 3DES: an eight-megabyte limit per key is a limit on files, not on messages, and it exists because a 64-bit block starts repeating.

The key sizes, side by side. Three-key 3DES has 168 bits of key material and about 112 bits of strength; AES-128 has 128 bits of both. 3DES therefore carries more key than AES-128 and is weaker, slower and limited to eight megabytes. There is no argument for choosing it.

Why a 64-bit block was the thing that finally killed it

The key length was never 3DES's problem. The block size was.

A 64-bit block cipher has 2 to the power 64 possible blocks. By the birthday reasoning of the hash chapters, after about 2 to the power 32 blocks encrypted under one key a repeated ciphertext block becomes likely, and in a chaining mode a repeat leaks a relationship between plaintext blocks. 2 to the power 32 blocks is 32 gigabytes, and practical attacks on long-lived connections were demonstrated well below that, which is why the standard's own limit is set two orders of magnitude lower still, at 2 to the power 20 blocks.

No key length fixes this. It is a property of the block size, and the only repair is a larger block, which means a different cipher. That is why AES uses 128 bits.

The dates, from the standards themselves

WhatWhenThe document's own words
Two-key 3DES encryption disallowedSP 800-131A Rev. 2, March 2019"Two-key TDEA Encryption Disallowed"
Two-key 3DES decryptionthe same"Legacy use"
Three-key 3DES encryption deprecatedthe same"Deprecated through 2023 / Disallowed after 2023"
Three-key 3DES decryptionthe same"Legacy use"
The per-key data limitSP 800-67 Rev. 2, November 2017no more than "2 to the power 20 64-bit blocks" under one key bundle
3DES no longer approved at all1 January 2024SP 800-67 Rev. 2 "is withdrawn in its entirety. This signifies that TDEA is no longer an approved block cipher."
What it may still be used forthe same withdrawal notice"decryption, key unwrapping, and verification of Message Authentication Codes (MACs) of already-protected data"
AES at all three key sizesSP 800-131A Rev. 2"Acceptable"

Read the second-to-last row carefully, because it is the shape of every retirement in this subject: decrypting old data stays permitted, protecting new data does not. A system cannot forget how to read what it wrote, so a withdrawn cipher lives on in a read-only role. The same pattern appears with SHA-1, with SSL, and with DSA.

munotes.in150

Triple DES, and Where It Stands Now

A worked example: what to do with a system that uses 3DES

The situation. A college's fee-payment records were encrypted with three-key 3DES in 2015 and the system is still running in 2026.

Step 1: is it broken? No. No practical attack recovers a 168-bit 3DES key, and the effective 112 bits is beyond reach.

Step 2: is it permitted? For new encryption, no. Three-key 3DES encryption was deprecated through 2023 and disallowed after, and since 1 January 2024 TDEA is not an approved block cipher at all.

Step 3: how much data is under each key? This is the question nobody asks and the one that matters. If a single key bundle has protected more than 2 to the power 20 blocks, that is eight megabytes, the standard's own limit has been exceeded and the birthday concern is real rather than theoretical.

Step 4: what to do. Keep the 3DES decryption path, because the old records must remain readable, and that use is still permitted. Encrypt everything new with AES, at 128 bits or more. Re-encrypt the old records under AES as they are next touched rather than in one operation, so that no key is stretched further.

The step that carries the marks. Steps 2 and 3 together. A question asking whether a system using 3DES is secure is asking three things: is there an attack, is it allowed, and how much data is under one key. All three have documented answers.

Distinctions that carry marks

Two-key 3DESThree-key 3DES
KeysK1, K2, with K3 equal to K1K1, K2, K3 independent
Key material112 bits168 bits
Effective strengthabout 80 bits against a known-plaintext attackabout 112 bits
Status since March 2019encryption disalloweddeprecated, then disallowed after 2023
3DESAES-128
Block size64 bits128 bits
Key material168 bits128 bits
Effective strengthabout 112 bits128 bits
Data per key2 to the power 20 blocks, 8 mebibyteseffectively unlimited for any real use
Speedabout three times slower than DESfaster than DES in software
Statusnot an approved block cipher since 1 January 2024acceptable

What beginners get wrong here

Saying 3DES is secure, full stop. Say that no practical attack is known and that it has not been an approved block cipher since 1 January 2024. Both halves.

Saying the middle decryption adds strength. It is for backward compatibility with single DES. The run proves that EDE with one key gives single DES and EEE does not.

Giving three-key 3DES 168 bits of strength. It has 168 bits of key material and about 112 bits of strength.

munotes.in151

Triple DES, and Where It Stands Now

Ignoring the block size. The 64-bit block, not the key, is what finally retired 3DES, and the 8-mebibyte limit per key is the practical consequence.

Thinking a withdrawn cipher must be switched off. Decryption of already-protected data remains permitted, by the withdrawal notice's own words. Only new protection is disallowed.

Quick revision

  • 3DES: C = E(K3, D(K2, E(K1, P))). Encrypt, decrypt, encrypt.
  • Why EDE: with all three keys equal it reduces to single DES, so DES equipment interoperates. Proved: 3FA40E8A984D4815 for EDE against 3B8FAD2B39E386BC for EEE.
  • Three-key: 168 bits of key material, about 112 bits of strength. Two-key (K3 equals K1): 112 bits of material, about 80 bits of strength.
  • It escapes meet-in-the-middle because one key must be guessed first, giving 2 to the power 112.
  • The block is 64 bits, so SP 800-67 Rev. 2 limits one key bundle to 2 to the power 20 blocks, which is 8,388,608 bytes or 8 mebibytes.
  • Dates: two-key encryption disallowed March 2019; three-key deprecated through 2023, disallowed after; 1 January 2024, SP 800-67 Rev. 2 withdrawn and "TDEA is no longer an approved block cipher".
  • Still permitted: decryption, key unwrapping and MAC verification of already-protected data.
  • AES-128 carries less key than three-key 3DES, is stronger, faster, and has no practical data limit.

Test yourself

1. Give the triple DES encryption and decryption formulas. Encryption is C = E(K3, D(K2, E(K1, P))) and decryption is P = D(K1, E(K2, D(K3, C))). The sequence is encrypt, decrypt, encrypt on the way out and the reverse on the way back.

2. Why is the middle stage a decryption rather than an encryption? For backward compatibility. Setting all three keys equal makes the second stage undo the first, so triple DES reduces exactly to single DES under that key and a 3DES device can interoperate with a DES device. It is not for security: DES decryption is no stronger than DES encryption. The chapter demonstrates both halves: EDE with one key gives the single-DES answer 3FA40E8A984D4815, and EEE gives 3B8FAD2B39E386BC, which is not single DES.

3. Distinguish two-key from three-key triple DES. Two-key triple DES sets K3 equal to K1, using 112 bits of key material with an effective strength of about 80 bits against a particular known-plaintext attack. Three-key triple DES uses three independent keys, 168 bits of material, with an effective strength of about 112 bits.

4. Why does triple DES resist the meet-in-the-middle attack when double DES does not? Because with three stages there is no intermediate value reachable from both ends without already knowing one of the keys. The attacker must therefore guess one key and then mount the meet-in-the-middle on the remaining two, multiplying the work by that key's space: about 2 to the power 112 in all for three-key triple DES.

munotes.in152

Triple DES, and Where It Stands Now

5. State the limit SP 800-67 Rev. 2 places on a key bundle, and why it exists. No more than 2 to the power 20 blocks of 64 bits may be protected under one key bundle, which is 1,048,576 blocks or 8,388,608 bytes, about 8 mebibytes. It exists because the block is only 64 bits, so repeated ciphertext blocks become likely as the volume grows and a repeat leaks a relationship between plaintexts. No key length removes the problem; only a larger block does.

6. What is the current status of triple DES? It has not been an approved block cipher since 1 January 2024, when NIST withdrew SP 800-67 Rev. 2 in its entirety, stating that TDEA is no longer an approved block cipher. Two-key encryption had already been disallowed in March 2019 and three-key encryption deprecated through 2023. Decryption, key unwrapping and verification of message authentication codes on already-protected data remain permitted.

7. Compare three-key triple DES with AES-128 and say which to choose. Three-key triple DES has 168 bits of key material against AES-128's 128, and is nevertheless weaker at about 112 bits of effective strength; it has a 64-bit block against AES's 128, which limits it to about 8 mebibytes per key; it is several times slower in software; and it is no longer approved. AES-128 is acceptable under current guidance. Choose AES.

Contents This chapter on its own page

munotes.in153

Chapter Twenty-Seven

Electronic Codebook Mode, and Why It Leaks

Syllabus topic Module 1, "Classical Encryption Techniques: Block Cipher Modes of Operation"

In one line

Encrypt every block independently with the same key. It is the simplest mode, it is the only one with no state at all, and it is the one you must not use, because equal plaintext blocks give equal ciphertext blocks.

In the wording a student can write in an examination: in electronic codebook mode the plaintext is divided into blocks of the cipher's block size and each block is encrypted independently under the same key, so that Cj = E(K, Pj) for every block j. The name comes from the fact that the mode is equivalent to having a codebook: for a given key, each possible plaintext block has one fixed ciphertext block. Its defect follows immediately: identical plaintext blocks produce identical ciphertext blocks, so any repetition in the plaintext is visible in the ciphertext.

Why a mode of operation is needed at all

A block cipher encrypts exactly one block. Real messages are longer than one block, shorter than one block, or not a whole number of blocks. A mode of operation is the rule for applying a block cipher to a message of arbitrary length, and the choice of mode matters as much as the choice of cipher: AES in ECB mode is a bad design using an excellent cipher.

Five modes are on this syllabus and SP 800-38A defines them: electronic codebook, cipher block chaining, cipher feedback, output feedback and counter. Each answers four questions differently, and the questions are the ones from the block-and-stream chapter: does it need padding, does the same plaintext give the same ciphertext, what does one corrupted bit destroy, and can a block in the middle be decrypted on its own.

The mode, the leak, and every vector in the standard

The cipher below is AES, written compactly with its substitution table as a literal rather than generated, because the three chapters on modes are about the modes. Encryption only is given, which is all any mode in this chapter needs. It is checked against FIPS 197 Appendix B on the listing's first line, before any claim is built on it.

And the listing ends by running all five of SP 800-38A's Appendix F families for AES-128, so that the next two chapters do not have to carry this cipher again and can spend their space on what each mode actually does.

"""AES, written compactly. The same cipher as the previous chapter's aes.py,
with the substitution table as a literal instead of generated, because this
chapter is about the MODE and not about how the table is built. Its output is
checked against FIPS 197 before any mode is run."""

SB = bytes.fromhex(
    "637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0"
    "b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b275"
    "09832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cf"
    "d0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2"
    "cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdb"
    "e0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08"
    "ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9e"
    "e1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb16")
ISB = bytes(256)
ISB = bytearray(256)
for _i, _v in enumerate(SB):
    ISB[_v] = _i
RCON = (0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36,
        0x6C, 0xD8, 0xAB, 0x4D)


def xt(a):
    a <<= 1
    return (a ^ 0x1B) & 0xFF if a & 0x100 else a


def gm(a, b):
    p = 0
    while b:
        if b & 1:
            p ^= a
        a, b = xt(a), b >> 1
    return p


def expand(key):
    nk, w = len(key) // 4, [list(key[4 * i:4 * i + 4]) for i in range(len(key) // 4)]
    nr = nk + 6
    for i in range(nk, 4 * (nr + 1)):
        t = list(w[i - 1])
        if i % nk == 0:
            t = [SB[b] for b in t[1:] + t[:1]]
            t[0] ^= RCON[i // nk - 1]
        elif nk > 6 and i % nk == 4:
            t = [SB[b] for b in t]
        w.append([w[i - nk][j] ^ t[j] for j in range(4)])
    return w, nr


def encrypt_block(block, key):
    w, nr = expand(key)
    s = list(block)
    for i in range(16):
        s[i] ^= w[i // 4][i % 4]
    for rnd in range(1, nr + 1):
        s = [SB[b] for b in s]
        s = [s[(i + 4 * (i % 4)) % 16] for i in range(16)]        # ShiftRows
        if rnd != nr:
            t = []
            for c in range(4):
                a = s[4 * c:4 * c + 4]
                t += [gm(a[0], 2) ^ gm(a[1], 3) ^ a[2] ^ a[3],
                      a[0] ^ gm(a[1], 2) ^ gm(a[2], 3) ^ a[3],
                      a[0] ^ a[1] ^ gm(a[2], 2) ^ gm(a[3], 3),
                      gm(a[0], 3) ^ a[1] ^ a[2] ^ gm(a[3], 2)]
            s = t
        for i in range(16):
            s[i] ^= w[rnd * 4 + i // 4][i % 4]
    return bytes(s)
munotes.in154

Electronic Codebook Mode, and Why It Leaks

import aesmini as aes

KEY = bytes.fromhex("2b7e151628aed2a6abf7158809cf4f3c")

print("FIPS 197 Appendix B, before anything else is claimed:",
      aes.encrypt_block(bytes.fromhex("3243f6a8885a308d313198a2e0370734"),
                        KEY).hex() == "3925841d02dc09fbdc118597196a0b32")
print()

def ecb(plain, key):
    return b"".join(aes.encrypt_block(plain[i:i + 16], key)
                    for i in range(0, len(plain), 16))

print("ECB against SP 800-38A Appendix F.1.1:")
pt = bytes.fromhex("6bc1bee22e409f96e93d7e117393172a"
                   "ae2d8a571e03ac9c9eb76fac45af8e51"
                   "30c81c46a35ce411e5fbc1191a0a52ef"
                   "f69f2445df4f9b17ad2b417be66c3710")
ct = ecb(pt, KEY)
for i in range(4):
    print("   block %d  %s  ->  %s"
          % (i + 1, pt[i * 16:(i + 1) * 16].hex(), ct[i * 16:(i + 1) * 16].hex()))
print("   the standard's four blocks are")
print("   3ad77bb40d7a3660a89ecaf32466ef97 f5d3d58503b9699de785895a96fdbaaf")
print("   43b1cd7f598ece23881b00e3ed030688 7b0c785e27e8ad3f8223207104725dd4")
print("   all four agree:", ct.hex() ==
      "3ad77bb40d7a3660a89ecaf32466ef97f5d3d58503b9699de785895a96fdbaaf"
      "43b1cd7f598ece23881b00e3ed0306887b0c785e27e8ad3f8223207104725dd4")
print()

print("now the leak. A student record of five fields, one value repeated:")
record = ((b"NAME=RAMESH PATIL" + b" " * 15)[:32]
          + b"MARKS=FAIL      " + b"MARKS=FAIL      " + b"MARKS=PASS      ")
for i in range(0, len(record), 16):
    print("   plaintext block %d: %r" % (i // 16 + 1, record[i:i + 16].decode()))
c = ecb(record, KEY)
print()
for i in range(0, len(c), 16):
    print("   ciphertext block %d: %s" % (i // 16 + 1, c[i:i + 16].hex()))
same = [(i // 16 + 1, j // 16 + 1)
        for i in range(0, len(c), 16) for j in range(i + 16, len(c), 16)
        if c[i:i + 16] == c[j:j + 16]]
print()
print("   identical ciphertext blocks:", same)
print("   an observer with NO KEY now knows that fields 3 and 4 hold the same")
print("   value, without knowing what that value is.")
print()

print("the same record chained, with an initialisation vector:")
IV = bytes.fromhex("000102030405060708090a0b0c0d0e0f")
out, prev = [], IV
for i in range(0, len(record), 16):
    prev = aes.encrypt_block(bytes(a ^ b for a, b in zip(prev, record[i:i + 16])), KEY)
    out.append(prev)
c2 = b"".join(out)
for i in range(0, len(c2), 16):
    print("   block %d: %s" % (i // 16 + 1, c2[i:i + 16].hex()))
same2 = [(i // 16 + 1, j // 16 + 1)
         for i in range(0, len(c2), 16) for j in range(i + 16, len(c2), 16)
         if c2[i:i + 16] == c2[j:j + 16]]
print("   identical ciphertext blocks:", same2 if same2 else "none")
print("   which is the next chapter.")

print()
print("and, once and for all, the whole of SP 800-38A Appendix F for AES-128,")
print("so that the next two chapters need not carry this cipher again:")

def xor(a, b):
    return bytes(x ^ y for x, y in zip(a, b))

IV2 = bytes.fromhex("000102030405060708090a0b0c0d0e0f")
NONCE = bytes.fromhex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")

def cbc(p, k, iv):
    o, prev = [], iv
    for i in range(0, len(p), 16):
        prev = aes.encrypt_block(xor(prev, p[i:i + 16]), k)
        o.append(prev)
    return b"".join(o)

def cfb(p, k, iv):
    o, shift = [], iv
    for i in range(0, len(p), 16):
        c = xor(p[i:i + 16], aes.encrypt_block(shift, k))
        o.append(c)
        shift = c
    return b"".join(o)

def ofb(p, k, iv):
    o, s = [], iv
    for i in range(0, len(p), 16):
        s = aes.encrypt_block(s, k)
        o.append(xor(p[i:i + 16], s))
    return b"".join(o)

def ctr(p, k, n):
    o, c = [], int.from_bytes(n, "big")
    for i in range(0, len(p), 16):
        o.append(xor(p[i:i + 16], aes.encrypt_block(c.to_bytes(16, "big"), k)))
        c += 1
    return b"".join(o)

for label, got, want in (
    ("F.1.1  ECB", ct,
     "3ad77bb40d7a3660a89ecaf32466ef97f5d3d58503b9699de785895a96fdbaaf"
     "43b1cd7f598ece23881b00e3ed0306887b0c785e27e8ad3f8223207104725dd4"),
    ("F.2.1  CBC", cbc(pt, KEY, IV2),
     "7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2"
     "73bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a7"),
    ("F.3.13 CFB", cfb(pt, KEY, IV2),
     "3b3fd92eb72dad20333449f8e83cfb4ac8a64537a0b3a93fcde3cdad9f1ce58b"
     "26751f67a3cbb140b1808cf187a4f4dfc04b05357c5d1c0eeac4c66f9ff7f2e6"),
    ("F.4.1  OFB", ofb(pt, KEY, IV2),
     "3b3fd92eb72dad20333449f8e83cfb4a7789508d16918f03f53c52dac54ed825"
     "9740051e9c5fecf64344f7a82260edcc304c6528f659c77866a510d9c1d6ae5e"),
    ("F.5.1  CTR", ctr(pt, KEY, NONCE),
     "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff"
     "5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee")):
    print("   %s  matches the standard: %s" % (label, got.hex() == want))
munotes.in155

Electronic Codebook Mode, and Why It Leaks

FIPS 197 Appendix B, before anything else is claimed: True

ECB against SP 800-38A Appendix F.1.1:
   block 1  6bc1bee22e409f96e93d7e117393172a  ->  3ad77bb40d7a3660a89ecaf32466ef97
   block 2  ae2d8a571e03ac9c9eb76fac45af8e51  ->  f5d3d58503b9699de785895a96fdbaaf
   block 3  30c81c46a35ce411e5fbc1191a0a52ef  ->  43b1cd7f598ece23881b00e3ed030688
   block 4  f69f2445df4f9b17ad2b417be66c3710  ->  7b0c785e27e8ad3f8223207104725dd4
   the standard's four blocks are
   3ad77bb40d7a3660a89ecaf32466ef97 f5d3d58503b9699de785895a96fdbaaf
   43b1cd7f598ece23881b00e3ed030688 7b0c785e27e8ad3f8223207104725dd4
   all four agree: True

now the leak. A student record of five fields, one value repeated:
   plaintext block 1: 'NAME=RAMESH PATI'
   plaintext block 2: 'L               '
   plaintext block 3: 'MARKS=FAIL      '
   plaintext block 4: 'MARKS=FAIL      '
   plaintext block 5: 'MARKS=PASS      '

   ciphertext block 1: bb3fd5c76d13d25006c63dd193c8fceb
   ciphertext block 2: 2b34d618f4de0a14bf62ab7db3255da3
   ciphertext block 3: dd45f549610b679c6821498d17416abf
   ciphertext block 4: dd45f549610b679c6821498d17416abf
   ciphertext block 5: 3956e719fce9244ea01ef1af333c90a7

   identical ciphertext blocks: [(3, 4)]
   an observer with NO KEY now knows that fields 3 and 4 hold the same
   value, without knowing what that value is.

the same record chained, with an initialisation vector:
   block 1: e5cf65dc43754834d67419805376a82e
   block 2: 8777caf259667a4b1cf24e22a91b26c3
   block 3: 23325366fc8cf10e33f63e1f872d875a
   block 4: 605bdde54b0c171830f69bbd7f1b40f3
   block 5: a3540b0df323ee1ec23d38f909edce0b
   identical ciphertext blocks: none
   which is the next chapter.

and, once and for all, the whole of SP 800-38A Appendix F for AES-128,
so that the next two chapters need not carry this cipher again:
   F.1.1  ECB  matches the standard: True
   F.2.1  CBC  matches the standard: True
   F.3.13 CFB  matches the standard: True
   F.4.1  OFB  matches the standard: True
   F.5.1  CTR  matches the standard: True
munotes.in156

Electronic Codebook Mode, and Why It Leaks

Read five things out of that run.

The four standard ECB blocks match SP 800-38A F.1.1, which is the check that the cipher is correct before it is used to make any argument at all.

Ciphertext blocks 3 and 4 are the same sixteen bytes, dd45f549610b679c6821498d17416abf, twice. The plaintext held MARKS=FAIL twice, and the ciphertext says so. An observer with no key knows that two fields of that record are equal.

That is worth more than it looks. Suppose the observer knows the record format and holds a thousand such records. Every record whose blocks 3 and 4 match has the same value in both fields; every record sharing a block with one the observer can read shares that value. This is how ECB leaks in practice, and no key length helps, because the key was never the problem.

The same record chained has no repeated blocks at all. Five distinct ciphertext blocks from a plaintext containing a repeat, which is the next chapter.

And all five modes agree with the standard, F.1.1, F.2.1, F.3.13, F.4.1 and F.5.1. The implementations of the other four are only a few lines each on top of the same cipher, which is itself the point: a mode is a wrapper, and the cipher underneath does not change.

When ECB is acceptable

Rarely, and it is worth being precise rather than saying "never", because a question may ask.

A single block. If the message is exactly one block long, ECB and CBC are the same thing apart from the initialisation vector, and there is no repetition to leak. Encrypting one key with another, which is what key wrapping does, is the standard case.

Nowhere else. SP 800-38A defines ECB and the security guidance around it has grown steadily narrower. For any message of more than one block, another mode is the answer.

munotes.in157

Electronic Codebook Mode, and Why It Leaks

A worked example: reading an ECB ciphertext without the key

The setting. A college portal stores each student's record as five 16-byte fields, encrypted field by field with AES in ECB mode under one key. The attacker has a copy of the encrypted table and knows the format.

Step 1: find the repeats. Sort the ciphertext blocks and count. Any ciphertext block appearing in many records corresponds to a plaintext value appearing in many records.

Step 2: guess the commonest values. In a results table, the commonest value in the result field will be PASS. The attacker does not need to decrypt anything; they need only assume that the most frequent ciphertext block is the most frequent plaintext value. This is frequency analysis, exactly as in the monoalphabetic chapter, with 16-byte blocks in place of letters.

Step 3: confirm with one known record. If the attacker knows their own record, they know their own fields, so every block of their own record is a known plaintext and ciphertext pair. Every other record sharing those blocks is read.

Step 4: what the attacker cannot do. They cannot read a value that appears only once and that they do not already know, and they cannot forge a record they have not seen a block of. So ECB is not a total break; it is a leak of equality.

The step that carries the marks. Step 2. ECB turns a block cipher into a monoalphabetic substitution cipher whose alphabet is the set of 16-byte blocks. Saying that sentence is the answer.

Distinctions that carry marks

ECB
RuleCj = E(K, Pj) for each block independently
State between blocksnone
Initialisation vectornone
Same plaintext, same ciphertextyes, and that is the defect
Padding neededyes
One corrupted ciphertext bit damagesits own block only
A lost blockaffects nothing else
Random accessyes, any block independently
Parallel encryptionyes
Suitable fora single block, such as a wrapped key
Not suitable foranything longer

What beginners get wrong here

Saying ECB is insecure because the cipher is weak. The cipher is whatever you chose, and AES in ECB mode leaks. The mode is the defect.

Thinking the leak needs a repeated whole message. It needs a repeated block. Structured data repeats blocks constantly: field names, padding, common values, empty fields.

Saying ECB must never be used. For a single block there is nothing to leak, and key wrapping is a real use. Give the exception and the reason.

Forgetting padding. ECB needs the message to be a whole number of blocks, so a padding rule is required, and the padding itself must be unambiguous.

munotes.in158

Electronic Codebook Mode, and Why It Leaks

Quick revision

  • ECB: Cj = E(K, Pj), each block independent, no state, no initialisation vector.
  • The defect: equal plaintext blocks give equal ciphertext blocks. Demonstrated: a record with MARKS=FAIL twice produced dd45f549610b679c6821498d17416abf twice.
  • It is effectively a monoalphabetic substitution cipher over 16-byte blocks, and frequency analysis applies.
  • The attacker learns equality without learning values, and no key length helps.
  • Needs padding; a corrupted bit damages its own block only; supports random access and parallel encryption.
  • Acceptable for a single block, such as wrapping one key with another. Not for anything longer.
  • The same record in CBC mode had no repeated ciphertext blocks.

Test yourself

1. State the ECB rule and its defining weakness. Each plaintext block is encrypted independently under the same key, Cj = E(K, Pj). The weakness is that identical plaintext blocks always produce identical ciphertext blocks, so any repetition in the plaintext is visible in the ciphertext to an attacker with no key.

2. Why is ECB called electronic codebook? Because for a fixed key the mode behaves like a codebook: every possible plaintext block has one fixed ciphertext block, so in principle an attacker could build a table of correspondences and look messages up in it.

3. What exactly does an attacker learn from an ECB ciphertext? That certain blocks of plaintext are equal to each other, and, if the attacker knows any plaintext and ciphertext pair, the value of every block equal to it. They learn equality rather than content, which for structured records is often enough to read most of the data.

4. Give the four properties of ECB an examination usually asks for. It needs padding; it is deterministic, so the same plaintext under the same key always gives the same ciphertext; a single corrupted ciphertext bit damages only its own block; and it supports random access and parallel processing because the blocks are independent.

5. Is there any legitimate use of ECB? Yes, for a message of exactly one block, where there is nothing to repeat. Wrapping one cryptographic key with another is the standard case. For any longer message another mode should be used.

6. Why does using AES rather than DES not fix ECB? Because the leak comes from the mode and not from the cipher. Whatever block cipher is used, encrypting each block independently under one key makes the mapping from plaintext block to ciphertext block fixed, so repeats show through. A longer key or a stronger cipher changes nothing about that.

7. Relate ECB to a cipher from earlier in this module. It is a monoalphabetic substitution cipher whose alphabet is the set of all blocks of the cipher's block size. Just as a monoalphabetic cipher over 26 letters preserves letter frequencies, ECB preserves block frequencies, and the same counting attack applies with blocks in place of letters.

Contents This chapter on its own page

munotes.in159

Chapter Twenty-Eight

Cipher Block Chaining

Syllabus topic Module 1, "Classical Encryption Techniques: Block Cipher Modes of Operation"

In one line

Exclusive-or each plaintext block with the previous ciphertext block before encrypting it. The chain removes the ECB leak, and an initialisation vector starts it, so the same plaintext under the same key gives a different ciphertext every time.

In the wording a student can write in an examination: in cipher block chaining the input to the encryption algorithm is the exclusive-or of the current plaintext block and the preceding ciphertext block, so that

C1 = E(K, P1 XOR IV)

Cj = E(K, Pj XOR C(j - 1))

P1 = D(K, C1) XOR IV

Pj = D(K, Cj) XOR C(j - 1)

The initialisation vector, or IV, starts the chain. It must be known to the receiver and, for security, must be unpredictable to an attacker; it need not be secret.

Why chaining works

Each ciphertext block depends on its own plaintext block and on every plaintext block before it. So two equal plaintext blocks in different positions are encrypted with different inputs, and their ciphertexts differ. The repetition that ECB exposes is gone, and the previous chapter's run demonstrated it: the record with MARKS=FAIL twice produced five distinct ciphertext blocks under CBC.

The chain also makes the mode stateful, and everything else about CBC follows from that.

What the initialisation vector is for

This is the part students get wrong, so it is worth stating as three separate facts.

The IV need not be secret. It is normally sent in the clear as the first block of the message. The receiver needs it, and an attacker who has it learns nothing they did not already have from the ciphertext.

The IV must be unpredictable. If an attacker can predict the IV of the next message, they can mount a chosen-plaintext attack that distinguishes plaintexts. So the IV should be random, or generated so that it cannot be guessed in advance. The commonest real failure of CBC is a predictable IV, not a weak cipher.

A fresh IV is what makes CBC non-deterministic. The run below encrypts one plaintext under two different IVs and gets two unrelated ciphertexts. That is the property that lets the same message be sent twice without an observer noticing, and it is the reason to use a new IV per message and never a fixed one.

The mode, and two attacks

The previous chapter ran AES against every vector in SP 800-38A Appendix F, CBC's included, so this chapter needs only a block cipher and uses a small one with a four-byte block. Four bytes print as four readable characters, so the attack below can be watched happening rather than inferred from hexadecimal. The listing proves the small cipher is a bijection before it uses it, because a demonstration built on a function that loses information would prove nothing.

munotes.in160

Cipher Block Chaining

# The demonstrations below need only A block cipher, and the previous chapter
# already ran AES against the standard's own vectors. So this one uses a small
# cipher with a FOUR-BYTE block, built as a Feistel network, because a four-byte
# block prints as four readable characters and the attack can be watched.

def f(half, subkey):
    x = (int.from_bytes(half, "big") * 0x9E37 + subkey) & 0xFFFF
    x ^= (x >> 7)
    return (x & 0xFFFF).to_bytes(2, "big")

def rounds(block, subkeys):
    left, right = block[:2], block[2:]
    for k in subkeys:
        left, right = right, bytes(a ^ b for a, b in zip(left, f(right, k)))
    return right + left

def encrypt_block(block, key):
    return rounds(block, [key & 0xFFFF, key >> 16, (key * 3) & 0xFFFF, key >> 8 & 0xFFFF])

def decrypt_block(block, key):
    return rounds(block, [key & 0xFFFF, key >> 16, (key * 3) & 0xFFFF, key >> 8 & 0xFFFF][::-1])

KEY = 0x2B7E1516
IV = b"\x01\x23\x45\x67"

# A cipher that is not a bijection is not a cipher. Prove it first.
seen = {encrypt_block(n.to_bytes(4, "big"), KEY) for n in range(0, 1 << 20)}
print("distinct outputs over the first 2 to the power 20 inputs:", len(seen))
print("decrypt undoes encrypt:",
      all(decrypt_block(encrypt_block(n.to_bytes(4, "big"), KEY), KEY)
          == n.to_bytes(4, "big") for n in range(0, 1 << 16, 97)))
print()

def xor(a, b):
    return bytes(x ^ y for x, y in zip(a, b))

def cbc_encrypt(plain, key, iv):
    out, prev = [], iv
    for i in range(0, len(plain), 4):
        prev = encrypt_block(xor(prev, plain[i:i + 4]), key)
        out.append(prev)
    return b"".join(out)

def cbc_decrypt(ct, key, iv):
    out, prev = [], iv
    for i in range(0, len(ct), 4):
        block = ct[i:i + 4]
        out.append(xor(decrypt_block(block, key), prev))
        prev = block
    return b"".join(out)

MSG = b"PAY 0500 TO 8817"
print("plaintext :", MSG.decode(), " in four-byte blocks:",
      " ".join(repr(MSG[i:i + 4].decode()) for i in range(0, 16, 4)))
CT = cbc_encrypt(MSG, KEY, IV)
print("ciphertext:", CT.hex())
print("decrypts  :", cbc_decrypt(CT, KEY, IV).decode())
print()

print("a fresh initialisation vector gives an unrelated ciphertext:")
for iv in (IV, b"\xff\xee\xdd\xcc"):
    print("   IV %s -> %s" % (iv.hex(), cbc_encrypt(MSG, KEY, iv).hex()))
print()

print("error propagation: one bit of ciphertext block 2 corrupted")
bad = bytearray(CT)
bad[5] ^= 0b00000001
got = cbc_decrypt(bytes(bad), KEY, IV)
print("   sent     :", MSG.decode())
print("   received :", repr(got))
for n in range(4):
    a, b = MSG[n * 4:n * 4 + 4], got[n * 4:n * 4 + 4]
    print("     block %d: %-8r %s %-10r  bits wrong: %d"
          % (n + 1, a.decode(), "->", b.decode("latin-1"),
             sum(bin(x ^ y).count("1") for x, y in zip(a, b))))
print("   block 2 is destroyed and block 3 has exactly one bit wrong.")
print("   Blocks 1 and 4 are untouched, so CBC recovers after two blocks.")
print()

print("the bit-flipping attack: the attacker chooses what block 4 says")
wanted = b"9999"
delta = xor(MSG[12:16], wanted)
attacked = bytearray(CT)
for i in range(4):
    attacked[8 + i] ^= delta[i]
out = cbc_decrypt(bytes(attacked), KEY, IV)
print("   block 4 said      :", repr(MSG[12:16].decode()))
print("   attacker wanted   :", repr(wanted.decode()))
print("   they changed block 3 of the CIPHERTEXT, by", delta.hex())
print("   receiver decrypts :", repr(out.decode("latin-1")))
print("   block 4 is now    :", repr(out[12:16].decode()), " as wanted:",
      out[12:16] == wanted)
print("   block 3 became    :", repr(out[8:12].decode("latin-1")))
print()
print("no key was used and no cipher was broken, because P4 = D(K, C4) XOR C3,")
print("so any change to C3 appears unchanged in P4.")
print("CBC gives confidentiality and NOT integrity.")
munotes.in161

Cipher Block Chaining

distinct outputs over the first 2 to the power 20 inputs: 1048576
decrypt undoes encrypt: True

plaintext : PAY 0500 TO 8817  in four-byte blocks: 'PAY ' '0500' ' TO ' '8817'
ciphertext: c97171d3853cc84cad2814776d3f4d1e
decrypts  : PAY 0500 TO 8817

a fresh initialisation vector gives an unrelated ciphertext:
   IV 01234567 -> c97171d3853cc84cad2814776d3f4d1e
   IV ffeeddcc -> a69012c96aca3d8abd69134f700ca94c

error propagation: one bit of ciphertext block 2 corrupted
   sent     : PAY 0500 TO 8817
   received : b'PAY \xf1;\x1d\xec UO 8817'
     block 1: 'PAY '   -> 'PAY '      bits wrong: 0
     block 2: '0500'   -> 'ñ;\x1dì'   bits wrong: 15
     block 3: ' TO '   -> ' UO '      bits wrong: 1
     block 4: '8817'   -> '8817'      bits wrong: 0
   block 2 is destroyed and block 3 has exactly one bit wrong.
   Blocks 1 and 4 are untouched, so CBC recovers after two blocks.

the bit-flipping attack: the attacker chooses what block 4 says
   block 4 said      : '8817'
   attacker wanted   : '9999'
   they changed block 3 of the CIPHERTEXT, by 0101080e
   receiver decrypts : 'PAY 0500 pP~9999'
   block 4 is now    : '9999'  as wanted: True
   block 3 became    : ' pP~'

no key was used and no cipher was broken, because P4 = D(K, C4) XOR C3,
so any change to C3 appears unchanged in P4.
CBC gives confidentiality and NOT integrity.

Read five things out of that run.

The small cipher is a cipher. 1,048,576 distinct outputs from 1,048,576 inputs, and decryption undoes encryption. That check comes first.

Two initialisation vectors give two unrelated ciphertexts for the same plaintext and key. CBC is not deterministic, and that is what the IV is for.

A corrupted bit in ciphertext block 2 destroys plaintext block 2 and flips exactly one bit of block 3. The program counts the bit errors per block and prints them: 0, 15, 1, 0. And the reader can see it: TO came back as UO . The reason is the decryption equation Pj = D(K, Cj) XOR C(j-1): a changed bit of C(j-1) passes straight through the exclusive-or into Pj, while a changed bit of Cj goes through the inverse cipher and ruins the whole block. Blocks 1 and 4 are untouched, so CBC is self-synchronising: it recovers after two blocks.

munotes.in162

Cipher Block Chaining

The bit-flipping attack worked and the receiver read the attacker's text. PAY 0500 TO 8817 became PAY 0500 pP~9999 in the receiver's hands. The attacker exclusive-ored 0101080e, the difference between 8817 and 9999, into ciphertext block 3, and block 4 came out as 9999. No key was used and no cipher was broken. The price was block 3, which became rubbish, and in most real message formats a sacrificed block is a header, a timestamp or a field the application ignores.

So confidentiality is not integrity, and CBC gives only the first. This is why every modern protocol carries a message authentication code over the ciphertext, and it is what Module 1's later chapters on MACs are for. "Is CBC secure" is answered: secure for confidentiality given an unpredictable, fresh IV, and no protection whatever against modification.

A worked example, by hand

The setting. Block size 8 bits for arithmetic that fits on a page. Plaintext blocks P1 = 10110010 and P2 = 10110010, the same block twice. IV is 01100101. Take the block cipher to be a function E whose values are given.

Step 1: block 1. P1 XOR IV is 10110010 XOR 01100101, which is 11010111. That goes into the cipher, and suppose E(K, 11010111) is 00101101. So C1 = 00101101.

Step 2: block 2. P2 XOR C1 is 10110010 XOR 00101101, which is 10011111. Suppose E(K, 10011111) is 11110000. So C2 = 11110000.

Step 3: the point. P1 and P2 were identical and C1 and C2 are not, because the second was encrypted after being exclusive-ored with the first ciphertext block. Under ECB both would have been 00101101.

Step 4: decrypt block 2. D(K, C2) gives back 10011111, and exclusive-oring C1 gives 10011111 XOR 00101101, which is 10110010, which is P2. The receiver needs C1, which they have, and does not need P1.

Distinctions that carry marks

ECBCBC
RuleCj = E(K, Pj)Cj = E(K, Pj XOR C(j-1))
Statenonethe previous ciphertext block
Initialisation vectornonerequired, unpredictable, not secret
Same plaintext, same ciphertextyesno, with a fresh IV
Repeated blocks visibleyesno
One corrupted ciphertext bit damagesits own blockits own block, and one bit of the next
Encryption can be parallelyesno, it is sequential
Decryption can be parallelyesyes
Random access to decrypt block jyesyes, given block j and j-1
Padding neededyesyes
Protects against modificationnono
munotes.in163

Cipher Block Chaining

Two rows are worth dwelling on. Encryption is sequential and decryption is parallel, because encrypting block j needs C(j-1) but decrypting it needs only Cj and C(j-1), both of which are already in hand. And the last row is the same for both modes: neither gives integrity.

What beginners get wrong here

Thinking the IV must be secret. It must be unpredictable. It is normally sent in the clear.

Reusing one IV for every message. That makes CBC deterministic for the first block and leaks whether two messages begin the same way.

Saying a corrupted bit destroys the rest of the message. It destroys its own block and one bit of the next, and then the chain recovers. CBC is self-synchronising.

Saying CBC provides integrity because a change is detected. A change produces different plaintext; it does not produce an error. Unless the application can tell that the plaintext is wrong, nothing is detected, and the bit-flipping attack shows an attacker choosing exactly what the wrong plaintext says.

Forgetting that encryption cannot be parallelised. It is the practical cost of CBC and the reason counter mode is preferred for high throughput.

Quick revision

  • CBC: C1 = E(K, P1 XOR IV), Cj = E(K, Pj XOR C(j-1)). Decrypt with Pj = D(K, Cj) XOR C(j-1).
  • The IV must be unpredictable, need not be secret, and must be fresh per message. A fresh IV is what makes CBC non-deterministic.
  • Chaining removes the ECB leak: equal plaintext blocks give different ciphertext blocks.
  • One corrupted ciphertext bit: its own plaintext block is destroyed and one bit of the next flips. ACCOUNT became @CCOUNT. Then the chain recovers, so CBC is self-synchronising.
  • Encryption is sequential; decryption is parallel.
  • The bit-flipping attack: changing ciphertext block j-1 lets an attacker choose the plaintext of block j exactly, destroying block j-1 in the process. Performed in this chapter with no key.
  • CBC gives confidentiality and no integrity. Add a message authentication code.

Test yourself

1. Give the CBC encryption and decryption equations. C1 = E(K, P1 XOR IV) and Cj = E(K, Pj XOR C(j - 1)) for later blocks. Decryption is P1 = D(K, C1) XOR IV and Pj = D(K, Cj) XOR C(j - 1).

2. What are the requirements on the initialisation vector? It must be available to the receiver, so it is usually sent in the clear; it must be unpredictable to an attacker, so it should be random or otherwise unguessable; and it must be fresh for each message, because reusing it makes the mode deterministic and leaks whether two messages share a beginning. It need not be secret.

munotes.in164

Cipher Block Chaining

3. How does CBC remove the weakness of ECB? By making each ciphertext block depend on all preceding plaintext blocks. Two identical plaintext blocks are exclusive-ored with different preceding ciphertext blocks before encryption, so their ciphertexts differ and the repetition is no longer visible.

4. What is the effect of one corrupted bit in a CBC ciphertext? The plaintext block corresponding to that ciphertext block decrypts to rubbish, because the corrupted bit passes through the inverse cipher; and exactly the same one bit of the following plaintext block flips, because the corrupted ciphertext block is exclusive-ored into it directly. Later blocks are unaffected, so the mode is self-synchronising.

5. Describe the bit-flipping attack on CBC and what it shows. An attacker who knows or guesses plaintext block j exclusive-ors into ciphertext block j-1 the difference between that plaintext and the plaintext they want. The receiver then decrypts block j as the attacker's chosen text, at the cost of block j-1 becoming rubbish. It shows that CBC provides confidentiality only: the ciphertext can be modified to produce a chosen plaintext without any knowledge of the key, so a separate integrity mechanism is required.

6. Why can CBC decryption be parallelised when encryption cannot? Because encrypting block j needs C(j-1), which is not known until block j-1 has been encrypted, so encryption must proceed in order. Decrypting block j needs only Cj and C(j-1), both of which are already present in the received ciphertext, so every block can be decrypted at once.

7. Is CBC a good choice today? Give a qualified answer. For confidentiality with an unpredictable, fresh initialisation vector it is sound and widely used. It provides no integrity, so it must be combined with a message authentication code or replaced by an authenticated mode; and its encryption is sequential, so counter mode is preferred where throughput matters.

Contents This chapter on its own page

munotes.in165

Chapter Twenty-Nine

Cipher Feedback, Output Feedback and Counter Mode

Syllabus topic Module 1, "Classical Encryption Techniques: Block Cipher Modes of Operation"

In one line

Use the block cipher to make a key stream, then exclusive-or it with the data. All three modes do that, so all three turn a block cipher into a stream cipher; they differ only in what goes into the cipher next.

In the wording a student can write in an examination:

Cipher feedback encrypts the shift register, takes the leftmost s bits, exclusive-ors them with s bits of plaintext to give s bits of ciphertext, and shifts that ciphertext into the register.

Output feedback is the same except that it shifts the cipher's own output into the register, so the key stream does not depend on the data at all.

Counter mode encrypts a counter that increases by one for each block, and exclusive-ors the result with the plaintext; no feedback of any kind.

CFB: Cj = Pj XOR E(K, shift), shift takes Cj

OFB: Oj = E(K, O(j - 1)), Cj = Pj XOR Oj

CTR: Cj = Pj XOR E(K, counter + j - 1)

What the three have in common, and why it matters

All three use the block cipher in the encryption direction only, even when decrypting. That is worth saying first because it explains several of the properties at once.

No padding. The key stream is as long as you need and no longer, so a message of seven bytes gives seven bytes of ciphertext. The run below shows it for all three.

The decryption code is the encryption code. For OFB and CTR literally so, because exclusive-or is its own inverse; for CFB nearly so, with only the feedback source changing. A hardware implementation needs only the forward cipher.

No integrity, and a worse failure mode than CBC's. Flipping a ciphertext bit flips exactly the corresponding plaintext bit, with nothing else disturbed. That is convenient for the honest and perfect for an attacker who knows the plaintext: they can change it to anything of the same length. These modes need a message authentication code more urgently than CBC does, not less.

The three modes, run

The ECB chapter ran all five of SP 800-38A's Appendix F families against AES, these three included, so this chapter needs only a block cipher and uses a small one with a two-byte block. Two bytes print as four hexadecimal digits, so the key stream can be watched arriving block by block, which is the thing the diagrams cannot show.

# The previous two chapters ran AES against every vector in SP 800-38A
# Appendix F, so this chapter needs only A block cipher. It uses a small one
# with a TWO-BYTE block, because two bytes print as four hex digits and the
# key stream can be watched arriving.

def block_cipher(block, key):
    """A 16-bit block, four rounds of multiply, rotate and mix. Encryption only,
    which is all any of these three modes ever needs."""
    x = int.from_bytes(block, "big")
    for r in range(4):
        x = (x * 0x2545 + key + r) & 0xFFFF
        x = ((x << 7) | (x >> 9)) & 0xFFFF
        x ^= (key >> (4 * r)) & 0xFFFF
    return x.to_bytes(2, "big")

KEY = 0x2B7E1516
IV = b"\x01\x23"
NONCE = 0xF0F1

def xor(a, b):
    return bytes(x ^ y for x, y in zip(a, b))

def cfb(data, key, iv, decrypting=False):
    out, shift = [], iv
    for i in range(0, len(data), 2):
        chunk = data[i:i + 2]
        c = xor(chunk, block_cipher(shift, key)[:len(chunk)])
        out.append(c)
        shift = chunk if decrypting else c
    return b"".join(out)

def ofb_stream(key, iv, n):
    out, s = b"", iv
    while len(out) < n:
        s = block_cipher(s, key)
        out += s
    return out[:n]

def ctr_stream(key, nonce, n, start=0):
    out, c = b"", nonce + start
    while len(out) < n:
        out += block_cipher((c & 0xFFFF).to_bytes(2, "big"), key)
        c += 1
    return out[:n]

print("the first four key stream blocks each mode produces:")
print("   OFB:", " ".join(ofb_stream(KEY, IV, 8)[i:i+2].hex() for i in range(0, 8, 2)))
print("   CTR:", " ".join(ctr_stream(KEY, NONCE, 8)[i:i+2].hex() for i in range(0, 8, 2)))
print("   CFB depends on the data, so it has no key stream of its own.")
print()

MSG = b"PAY 0500 TO 8817"
print("plaintext:", MSG.decode())
c_cfb = cfb(MSG, KEY, IV)
c_ofb = xor(MSG, ofb_stream(KEY, IV, len(MSG)))
c_ctr = xor(MSG, ctr_stream(KEY, NONCE, len(MSG)))
print("   CFB:", c_cfb.hex())
print("   OFB:", c_ofb.hex())
print("   CTR:", c_ctr.hex())
print()
print("CFB and OFB agree on the FIRST block and nowhere else:")
for i in range(0, 8, 2):
    a, b = c_cfb[i:i+2].hex(), c_ofb[i:i+2].hex()
    print("   block %d  CFB %s  OFB %s  same: %s" % (i // 2 + 1, a, b, a == b))
print("   both begin as P XOR E(IV); then CFB feeds back the CIPHERTEXT and")
print("   OFB feeds back its OWN OUTPUT, so the streams diverge at once.")
print()

print("all three decrypt with the SAME operation, so no inverse cipher is needed:")
print("   CFB:", cfb(c_cfb, KEY, IV, decrypting=True).decode())
print("   OFB:", xor(c_ofb, ofb_stream(KEY, IV, len(c_ofb))).decode())
print("   CTR:", xor(c_ctr, ctr_stream(KEY, NONCE, len(c_ctr))).decode())
print()

print("no padding: a message of any length gives a ciphertext of that length")
for n in (1, 3, 7):
    print("   %d byte(s) in -> CFB %d, OFB %d, CTR %d out"
          % (n, len(cfb(MSG[:n], KEY, IV)),
             len(xor(MSG[:n], ofb_stream(KEY, IV, n))),
             len(xor(MSG[:n], ctr_stream(KEY, NONCE, n)))))
print()

print("CTR gives random access. Block 5 alone, without blocks 1 to 4:")
print("   plaintext block 5           :", repr(MSG[8:10].decode()))
print("   recovered from block 5 only :",
      repr(xor(c_ctr[8:10], ctr_stream(KEY, NONCE, 2, start=4)).decode()))
print()

print("error propagation, one bit of the first ciphertext byte flipped:")
for name, ct, dec in (("CFB", c_cfb, lambda d: cfb(d, KEY, IV, decrypting=True)),
                      ("OFB", c_ofb, lambda d: xor(d, ofb_stream(KEY, IV, len(d)))),
                      ("CTR", c_ctr, lambda d: xor(d, ctr_stream(KEY, NONCE, len(d))))):
    bad = bytearray(ct)
    bad[0] ^= 1
    got = dec(bytes(bad))
    print("   %s: %r" % (name, got.decode("latin-1")))
    print("        %d byte(s) and %d bit(s) of the plaintext wrong"
          % (sum(1 for a, b in zip(MSG, got) if a != b),
             sum(bin(a ^ b).count("1") for a, b in zip(MSG, got))))
print("   CFB loses the next block too, because the corrupted ciphertext is fed")
print("   back. OFB and CTR lose one bit, because their streams never see it.")
print()

print("and the one rule these two share with the one-time pad:")
a = xor(b"ATTACK AT DAWN!!", ctr_stream(KEY, NONCE, 16))
b = xor(b"HOLD POSITION!!!", ctr_stream(KEY, NONCE, 16))
print("   c1 XOR c2 :", xor(a, b).hex())
print("   p1 XOR p2 :", xor(b"ATTACK AT DAWN!!", b"HOLD POSITION!!!").hex())
print("   identical :", xor(a, b) == xor(b"ATTACK AT DAWN!!", b"HOLD POSITION!!!"))
print("   the key has vanished. NEVER encrypt twice under one counter value.")
munotes.in166

Cipher Feedback, Output Feedback and Counter Mode

the first four key stream blocks each mode produces:
   OFB: 477d 37dd 2e91 7686
   CTR: a9bb 29c6 d6ce b889
   CFB depends on the data, so it has no key stream of its own.

plaintext: PAY 0500 TO 8817
   CFB: 173c218fcf36e80bb47acfa72c06bb0c
   OFB: 173c6efd1ea446b68832a6b052cd44be
   CTR: f9fa70e6e6fb88b9cee9d5b5466a1332

CFB and OFB agree on the FIRST block and nowhere else:
   block 1  CFB 173c  OFB 173c  same: True
   block 2  CFB 218f  OFB 6efd  same: False
   block 3  CFB cf36  OFB 1ea4  same: False
   block 4  CFB e80b  OFB 46b6  same: False
   both begin as P XOR E(IV); then CFB feeds back the CIPHERTEXT and
   OFB feeds back its OWN OUTPUT, so the streams diverge at once.

all three decrypt with the SAME operation, so no inverse cipher is needed:
   CFB: PAY 0500 TO 8817
   OFB: PAY 0500 TO 8817
   CTR: PAY 0500 TO 8817

no padding: a message of any length gives a ciphertext of that length
   1 byte(s) in -> CFB 1, OFB 1, CTR 1 out
   3 byte(s) in -> CFB 3, OFB 3, CTR 3 out
   7 byte(s) in -> CFB 7, OFB 7, CTR 7 out

CTR gives random access. Block 5 alone, without blocks 1 to 4:
   plaintext block 5           : ' T'
   recovered from block 5 only : ' T'

error propagation, one bit of the first ciphertext byte flipped:
   CFB: 'QA=40500 TO 8817'
        3 byte(s) and 6 bit(s) of the plaintext wrong
   OFB: 'QAY 0500 TO 8817'
        1 byte(s) and 1 bit(s) of the plaintext wrong
   CTR: 'QAY 0500 TO 8817'
        1 byte(s) and 1 bit(s) of the plaintext wrong
   CFB loses the next block too, because the corrupted ciphertext is fed
   back. OFB and CTR lose one bit, because their streams never see it.

and the one rule these two share with the one-time pad:
   c1 XOR c2 : 091b1805631b6f121d740d0e196f0000
   p1 XOR p2 : 091b1805631b6f121d740d0e196f0000
   identical : True
   the key has vanished. NEVER encrypt twice under one counter value.
munotes.in167

Cipher Feedback, Output Feedback and Counter Mode

Read six things out of that run.

munotes.in168

Cipher Feedback, Output Feedback and Counter Mode

OFB and CTR have a key stream of their own and CFB does not. The first four blocks of each are printed. CFB's stream depends on the ciphertext, so it cannot be computed until the data arrives; OFB's and CTR's can be generated in advance, which matters for a device that must encrypt at line speed.

CFB and OFB agree on block 1 and nowhere else. Both compute the first key stream block as E(K, IV), so the first ciphertext block is identical. From block 2 they diverge, because CFB's register has taken the ciphertext and OFB's has taken the cipher's own output. That is the whole difference between the two modes, and seeing it in the output fixes it in a way the diagrams do not.

All three decrypt with the same operation that encrypts, and all three recovered PAY 0500 TO 8817. No inverse cipher is needed anywhere, which is why these modes are cheap in hardware.

No padding, for any of the three. One, three and seven bytes in; one, three and seven out.

Only CTR gives random access. Block 5 of the plaintext was recovered from block 5 of the ciphertext alone, because its key stream block is E(K, counter + 4) and depends on nothing else. CFB needs the previous ciphertext block and OFB needs the stream from the beginning, so neither can start in the middle.

One flipped ciphertext bit costs CFB three bytes and OFB and CTR one bit. CFB: the flipped bit gives one wrong plaintext bit, and then that corrupted ciphertext is fed back, so the whole of the next block's key stream is wrong. OFB and CTR: one bit, and nothing else, because their key streams never touch the ciphertext.

And the rule. Two messages under the same counter value: c1 XOR c2 came out exactly equal to p1 XOR p2. The key cancels, as in the one-time pad chapter. So a counter value or an OFB initialisation vector must never repeat under one key. That is not a subtlety; it is the whole security of the mode.

A worked example: choosing the mode

Job 1: encrypt a 10 gigabyte disk image, where any sector may be read on its own.

Counter mode. Random access is the requirement, and only CTR has it: the key stream for sector n depends only on n. CBC would force a read of the previous block, and OFB would force generating the stream from the beginning.

munotes.in169

Cipher Feedback, Output Feedback and Counter Mode

Job 2: encrypt a terminal session, one character at a time, over a link that drops bits.

Cipher feedback with s of 8. It can encrypt one byte as soon as it arrives, and it is self-synchronising: after a lost or corrupted byte, the register refills with correct ciphertext after 16 bytes and the mode recovers on its own. OFB and CTR do not recover from a lost byte at all, because the key stream position no longer lines up.

Job 3: encrypt a satellite telemetry stream over a noisy channel where bits flip but none are lost.

Output feedback. A flipped bit affects exactly one plaintext bit, which is what an error-correcting code above it can repair. In CFB the same flipped bit would ruin the next sixteen bytes and defeat the correction.

Job 4: the general case, in a modern protocol.

Counter mode combined with an authentication tag, which is what Galois/Counter Mode is and what TLS 1.3 requires. The pattern to remember: CTR for the encryption, a MAC for the integrity, and never one without the other.

The step that carries the marks. Each answer names the property that decided it: random access, self-synchronisation, single-bit error confinement. An answer saying "CTR is the most modern" has not answered.

Distinctions that carry marks

CFBOFBCTR
Register inputthe ciphertextthe cipher's own outputa counter
Key stream depends on the datayesnono
Paddingnonenonenone
Cipher used in decryptionforward onlyforward onlyforward only
One flipped ciphertext bitits own bit, then the whole next blockits own bit onlyits own bit only
A lost byterecovers after one blocknever recoversnever recovers
Random accessnonoyes
Encryption parallelnonoyes
Key stream precomputablenoyesyes
Must never repeatthe IVthe IVthe counter value
Suitsa character-at-a-time linka noisy channelbulk data, disks, modern protocols
CBCCFBOFB and CTR
Turns the cipher intoa block cipher over the messagea stream ciphera stream cipher
Needs the inverse cipheryesnono
Needs paddingyesnono
Error in one ciphertext bitown block plus one bit of the nextown bit plus the next blockown bit only
Self-synchronisingyesyesno

What beginners get wrong here

Saying OFB and CTR "use decryption" to decrypt. They do not. Both use the cipher forwards in both directions, because the key stream is exclusive-ored either way.

Confusing CFB's and OFB's feedback. CFB feeds back the ciphertext; OFB feeds back the cipher's output. It is the only difference and it is the examinable one.

munotes.in170

Cipher Feedback, Output Feedback and Counter Mode

Saying OFB is self-synchronising. It is not. CFB is, because its register refills with received ciphertext; OFB's register is generated locally and never resynchronises.

Thinking a single-bit error is always better. It is better against noise and much worse against an attacker, who can flip exactly the bits they want in the plaintext. These modes need integrity protection more than CBC does.

Reusing a counter or an OFB initialisation vector. It is the one fatal error, and the run proves it: the key cancels and the difference of the plaintexts is exposed.

Quick revision

  • All three build a key stream and exclusive-or it with the data, use the cipher forwards only, and need no padding.
  • CFB shifts the ciphertext into the register. OFB shifts the cipher's own output. CTR encrypts a counter.
  • CFB and OFB agree on block 1 and diverge after, because both start from E(K, IV).
  • One flipped ciphertext bit: CFB its own bit plus the whole next block (17 bytes in the run); OFB and CTR one bit.
  • A lost byte: CFB recovers after a block; OFB and CTR never do.
  • Only CTR has random access and parallel encryption. Its key stream block for position j is E(K, counter + j - 1) and depends on nothing else.
  • OFB and CTR key streams are precomputable before the plaintext arrives.
  • A counter value or an OFB IV must never repeat under one key. Proved: c1 XOR c2 equals p1 XOR p2 and the key vanishes.
  • None of the three gives integrity; the modern answer is counter mode plus an authentication tag, which is what TLS 1.3 requires.

Test yourself

1. Give the defining rule of each of the three modes. Cipher feedback encrypts the shift register, takes the leftmost s bits as key stream, and shifts the resulting ciphertext back into the register. Output feedback does the same but shifts the cipher's own output back, so the key stream is independent of the data. Counter mode encrypts a counter that increases by one per block and exclusive-ors the result with the plaintext, with no feedback at all.

2. What is the single difference between CFB and OFB, and what does it cause? What is fed back: the ciphertext in CFB, the cipher's output in OFB. It causes every other difference: CFB's key stream depends on the data, so a transmission error propagates into the next block and the mode is self-synchronising; OFB's does not, so an error affects one bit only and the mode never resynchronises after a lost byte.

3. Why do none of these modes need padding? Because the block cipher is used to produce a key stream rather than to transform the plaintext directly. The stream is truncated to the length of the message and exclusive-ored with it, so the ciphertext is exactly as long as the plaintext.

munotes.in171

Cipher Feedback, Output Feedback and Counter Mode

4. Why is counter mode the only one of the three with random access? Because the key stream block for position j is E(K, counter + j - 1), computable from j alone. CFB needs the preceding ciphertext block and OFB needs the stream generated from the initialisation vector onwards, so neither can start in the middle.

5. Compare the damage from one flipped ciphertext bit across CBC, CFB, OFB and CTR. CBC: the whole of its own plaintext block plus the same one bit of the next. CFB: its own bit, and then the whole of the next block, because the corrupted ciphertext enters the register. OFB and CTR: that one bit and nothing else. In the chapter's run CFB lost 17 bytes where OFB and CTR each lost one.

6. State the one rule that must never be broken in OFB and CTR, and why. A counter value, or an output feedback initialisation vector, must never be used twice under the same key. If it is, two ciphertexts share a key stream, and exclusive-oring them removes the key entirely and leaves the exclusive-or of the two plaintexts. The chapter's run checks this: c1 XOR c2 equalled p1 XOR p2 exactly.

7. Which mode would you choose for a full-disk encryption product, and which for a character-at-a-time terminal link? Counter mode for the disk, because any sector must be readable on its own and its key stream depends only on the sector number. Cipher feedback with s of 8 for the terminal link, because a single character can be encrypted as soon as it is typed and the mode resynchronises by itself after a corrupted or lost byte.

Contents This chapter on its own page

munotes.in172

Chapter Thirty

Stream Ciphers and RC4

Syllabus topic Module 1, "Classical Encryption Techniques: Stream Ciphers"

In one line

Generate a key stream a byte at a time from a shuffled table of 256 values, and exclusive-or it with the data. It is the simplest serious cipher ever deployed, it was in almost everything, and it is now prohibited.

In the wording a student can write in an examination: a stream cipher encrypts a digital data stream one bit or one byte at a time, by generating a key stream from the key and combining it with the plaintext, usually by exclusive-or. RC4 is a stream cipher designed by Ron Rivest in 1987 with a variable key length from 1 to 256 bytes. It has two parts: the key scheduling algorithm, which uses the key to permute a 256-byte state array S; and the pseudorandom generation algorithm, which produces one key stream byte per call by swapping two entries of S and outputting a third.

The design conditions a key stream must meet

A stream cipher is only as good as its key stream, so the requirements are worth stating as a list, because that is how they are examined.

A long period. The key stream eventually repeats, and when it does, the key-reuse failure of the one-time pad chapter happens inside one message. A period of at least 2 to the power 128 is expected.

Statistically random output. The key stream must pass the same tests a true random sequence would: an equal number of ones and zeros, no correlation between positions, every byte value equally likely. RC4 fails this one, and section 6 measures by how much.

A key long enough to resist brute force. The same requirement as any cipher; at least 128 bits.

The key stream must depend on the whole key. A generator in which the first output bytes depend on only part of the key can be attacked piecewise, which is what happened to RC4 in WEP.

And the rule that is not a design condition but an operating condition, and the one that breaks real systems: a key stream must never be used twice. The one-time pad chapter proved why, and the run below proves it again for RC4.

The cipher, run against its own vectors and measured

# RC4, and why it is banned. The whole cipher is eleven lines.

def ksa(key):
    """The key scheduling algorithm: a keyed shuffle of the 256 byte values."""
    s = list(range(256))
    j = 0
    for i in range(256):
        j = (j + s[i] + key[i % len(key)]) % 256
        s[i], s[j] = s[j], s[i]
    return s

def prga(s, n):
    """The pseudorandom generation algorithm: the key stream, one byte at a time."""
    i = j = 0
    out = bytearray()
    for _ in range(n):
        i = (i + 1) % 256
        j = (j + s[i]) % 256
        s[i], s[j] = s[j], s[i]
        out.append(s[(s[i] + s[j]) % 256])
    return bytes(out)

def rc4(data, key):
    return bytes(a ^ b for a, b in zip(data, prga(ksa(key), len(data))))

print("RFC 6229's first test vector, key 0102030405:")
ks = prga(ksa(bytes.fromhex("0102030405")), 16)
print("   key stream bytes 0 to 15:", ks.hex())
print("   the RFC gives          : b2396305f03dc027ccc3524a0a1118a8")
print("   agrees:", ks.hex() == "b2396305f03dc027ccc3524a0a1118a8")
print()

msg = b"PAY 0500 TO 8817"
c = rc4(msg, b"Secret")
print("encryption and decryption are the same operation:")
print("   plaintext :", msg.decode())
print("   ciphertext:", c.hex())
print("   decrypted :", rc4(c, b"Secret").decode())
print()

print("the second byte of the key stream is BIASED. Over 200,000 random keys,")
print("counting how often each value appears in position 2:")
import random
random.seed(11)
counts = [0] * 256
TRIALS = 200000
for _ in range(TRIALS):
    k = bytes(random.randrange(256) for _ in range(16))
    counts[prga(ksa(k), 2)[1]] += 1
expected = TRIALS / 256
print("   expected count for any value : %.1f" % expected)
print("   count for the value 0        : %d" % counts[0])
print("   ratio                        : %.2f" % (counts[0] / expected))
top = sorted(range(256), key=lambda v: -counts[v])[:3]
print("   the three commonest values   :",
      ", ".join("%d (%d)" % (v, counts[v]) for v in top))
print()
print("   a fair generator would give every value about %.0f times." % expected)
print("   The value 0 appears about twice as often, which is Mantin and Shamir's")
print("   result: the second byte of RC4's output is 0 with probability about")
print("   2 in 256 rather than 1 in 256. That alone distinguishes RC4 from random.")
print()

print("and the rule RC4 shares with the one-time pad, which is what WEP broke:")
a = rc4(b"ATTACK AT DAWN!!", b"Secret")
b = rc4(b"HOLD POSITION!!!", b"Secret")
x = bytes(p ^ q for p, q in zip(a, b))
y = bytes(p ^ q for p, q in zip(b"ATTACK AT DAWN!!", b"HOLD POSITION!!!"))
print("   c1 XOR c2 :", x.hex())
print("   p1 XOR p2 :", y.hex())
print("   identical :", x == y, " so the key vanishes.")
munotes.in173

Stream Ciphers and RC4

RFC 6229's first test vector, key 0102030405:
   key stream bytes 0 to 15: b2396305f03dc027ccc3524a0a1118a8
   the RFC gives          : b2396305f03dc027ccc3524a0a1118a8
   agrees: True

encryption and decryption are the same operation:
   plaintext : PAY 0500 TO 8817
   ciphertext: 549532250c9d4b6961267f0ad4a388a5
   decrypted : PAY 0500 TO 8817

the second byte of the key stream is BIASED. Over 200,000 random keys,
counting how often each value appears in position 2:
   expected count for any value : 781.2
   count for the value 0        : 1555
   ratio                        : 1.99
   the three commonest values   : 0 (1555), 215 (856), 133 (850)

   a fair generator would give every value about 781 times.
   The value 0 appears about twice as often, which is Mantin and Shamir's
   result: the second byte of RC4's output is 0 with probability about
   2 in 256 rather than 1 in 256. That alone distinguishes RC4 from random.

and the rule RC4 shares with the one-time pad, which is what WEP broke:
   c1 XOR c2 : 091b1805631b6f121d740d0e196f0000
   p1 XOR p2 : 091b1805631b6f121d740d0e196f0000
   identical : True  so the key vanishes.
munotes.in174

Stream Ciphers and RC4

Read five things out of that run.

The vector matches RFC 6229. b2396305f03dc027ccc3524a0a1118a8 for the key 0102030405, which is the first line of the RFC's own table.

Encryption and decryption are the same operation. rc4(rc4(m, k), k) is m, because exclusive-or is its own inverse. That is true of every stream cipher and is why they are cheap.

The bias is real and it is large. Over 200,000 random 16-byte keys, the second key stream byte took the value 0 on 1,555 occasions where a fair generator would give about 781. The ratio is 1.99. This is Mantin and Shamir's result of 2001: the second output byte of RC4 is zero with probability about 2 in 256 rather than 1 in 256, and that single fact is enough to distinguish RC4's output from random, which is the formal definition of a broken stream cipher.

The third and second commonest values are unremarkable. 215 appeared 856 times and 133 appeared 850, both close to the expected 781. So the bias is specific to the value 0 in position 2 and is not noise in the measurement: one value is twice as likely and the rest are where they should be.

And the key stream must never repeat. Two messages under the key Secret gave c1 XOR c2 exactly equal to p1 XOR p2. The key vanished. This is the failure that destroyed WEP.

Why RC4 is banned, with the dates

What happenedWhenConsequence
RC4 designed at RSA Security, kept as a trade secret1987widely licensed
The algorithm leaked and was posted publicly1994free implementations everywhere, under the name ARCFOUR
Fluhrer, Mantin and Shamir publish the key scheduling weakness2001WEP is broken; a passive listener recovers a wireless key
Mantin and Shamir publish the second-byte bias2001RC4's output is distinguishable from random
Further biases found throughout the key streamto 2013plaintext recovery from many TLS sessions becomes feasible
RFC 7465, Prohibiting RC4 Cipher SuitesFebruary 2015"TLS clients MUST NOT include RC4 cipher suites in the ClientHello message" and servers must not select one

The WEP failure is worth understanding because it is the clearest example in this subject of a sound component destroyed by its use. WEP combined a 24-bit initialisation vector with a fixed secret key and fed the concatenation straight into RC4's key schedule. Three things followed. A 24-bit initialisation vector repeats after about 16 million packets, which a busy network reaches in hours, and a repeat means a reused key stream. The initialisation vector was sent in the clear, so an attacker knew part of every RC4 key. And the key schedule's weakness meant that knowing part of the key made the first key stream bytes predictable. None of those is a flaw in RC4's arithmetic; all three are flaws in how RC4 was used.

munotes.in175

Stream Ciphers and RC4

A worked example: tracing the key schedule by hand

The setting. Key 1 2 3, three bytes. Trace the first three steps of the key scheduling algorithm.

Step 0. S starts as the identity: S[0] is 0, S[1] is 1, and so on to S[255] is 255. And j starts at 0.

Step 1, i is 0. j becomes (0 + S[0] + key[0]) mod 256, which is (0 + 0 + 1) mod 256, which is 1. Swap S[0] and S[1]. Now S[0] is 1 and S[1] is 0.

Step 2, i is 1. j becomes (1 + S[1] + key[1]) mod 256, which is (1 + 0 + 2) mod 256, which is 3. Swap S[1] and S[3]. Now S[1] is 3 and S[3] is 0.

Step 3, i is 2. j becomes (3 + S[2] + key[2]) mod 256, which is (3 + 2 + 3) mod 256, which is 8. Swap S[2] and S[8]. Now S[2] is 8 and S[8] is 2.

The step that carries the marks. Notice that key[i mod keylen] cycles through the key, so a three-byte key is used about 85 times across the 256 steps, and notice that j accumulates: it is never reset, so each step depends on every step before it. Those two facts together are what makes the schedule a shuffle rather than a pattern, and they are also where the Fluhrer, Mantin and Shamir attack gets its grip: the early values of j depend on only the first few key bytes.

Distinctions that carry marks

Block cipherStream cipher
Processesa fixed blockone bit or byte
Needs paddingyesno
Same code for both directionsnoyes
Stateusually a small registera generator's whole internal state
Hardware costhigherlower
The fatal mistakeelectronic codebook modereusing the key stream
Examples hereDES, 3DES, AESthe one-time pad, RC4
One-time padRC4
Key streamtruly random, as long as the messagegenerated from a short key
Periodnone, it never repeatsvery long, but finite
Securityunconditionalcomputational, and now broken
Key lengththe message length1 to 256 bytes
Reusing the key streamfatalfatal
munotes.in176

Stream Ciphers and RC4

RC4's key scheduling algorithmRC4's pseudorandom generation algorithm
Runsonce, 256 stepsonce per output byte
Uses the keyyesno, only the state it left behind
Producesthe permuted state Sone key stream byte
Attacked byFluhrer, Mantin and Shamir, 2001the output biases

What beginners get wrong here

Saying RC4 is insecure because its key is short. The key may be up to 256 bytes. RC4 is insecure because its output is biased and because its key schedule leaks, not for want of key length.

Blaming RC4 for WEP. WEP's failures were a 24-bit initialisation vector that repeats, an initialisation vector sent in the clear and concatenated with the key, and no integrity protection. RC4 was the victim, not the culprit, although its key schedule made the last step easy.

Thinking a stream cipher needs a separate decryption routine. It does not. The same function encrypts and decrypts.

Forgetting that the key stream must never repeat. It is the single operating rule, and the run demonstrates the consequence.

Claiming RC4 is still acceptable somewhere. RFC 7465 of February 2015 prohibits it in TLS in terms: clients must not offer RC4 cipher suites and servers must not select one.

Quick revision

  • A stream cipher generates a key stream from the key and exclusive-ors it with the data, one bit or byte at a time. Encryption and decryption are the same operation.
  • Design conditions: a long period, statistically random output, a key long enough to resist brute force, and the key stream must depend on the whole key.
  • The operating condition: never use a key stream twice.
  • RC4, Rivest 1987, key 1 to 256 bytes. KSA permutes a 256-byte array once using the key; PRGA then emits one byte per call, swapping two entries and outputting a third, and never looks at the key again.
  • Verified against RFC 6229: key 0102030405 gives b2396305f03dc027ccc3524a0a1118a8.
  • Measured bias: over 200,000 random keys the second output byte was 0 on 1,555 occasions against an expected 781, a ratio of 1.99, matching Mantin and Shamir's factor of 2.
  • Prohibited in TLS by RFC 7465, February 2015.
  • WEP failed by reusing key streams: a 24-bit initialisation vector that repeats within hours, sent in the clear and concatenated with the key.

Test yourself

1. Define a stream cipher and give RC4's two parts. A stream cipher encrypts a data stream one bit or byte at a time by generating a key stream from the key and combining it with the plaintext, normally by exclusive-or. RC4 has a key scheduling algorithm, which uses the key to permute a 256-byte state array in 256 steps, and a pseudorandom generation algorithm, which produces one key stream byte per call by swapping two entries of that array and outputting a third.

munotes.in177

Stream Ciphers and RC4

2. State the design conditions for a key stream generator. The period must be very long, at least 2 to the power 128, because a repeat causes key stream reuse within a single message. The output must be statistically indistinguishable from random. The key must be long enough to resist exhaustive search, at least 128 bits. And the key stream must depend on the whole key, so that knowing part of the key does not make part of the stream predictable.

3. What is the bias in RC4's output, and what does it mean? The second byte of the key stream takes the value zero with probability about 2 in 256 rather than the 1 in 256 a fair generator would give. The chapter measures it over 200,000 random keys and finds 1,555 occurrences against an expected 781, a ratio of 1.99. It means RC4's output is distinguishable from random, which is the formal definition of a broken stream cipher, and it allows plaintext recovery from many sessions encrypting the same data.

4. Why is encryption the same as decryption in a stream cipher? Because the plaintext is combined with the key stream by exclusive-or, and exclusive-or is its own inverse: applying the same key stream to the ciphertext returns the plaintext. The key stream is generated from the key alone and does not depend on the data, so the receiver can produce the identical stream.

5. Explain how WEP failed, and say whether RC4 was to blame. WEP concatenated a 24-bit initialisation vector, sent in the clear, with a fixed secret key, and used the result as an RC4 key. A 24-bit value repeats after about 16 million packets, which a busy network reaches within hours, so key streams were reused and exclusive-oring two ciphertexts removed the key. The initialisation vector being public also gave an attacker part of every key, and RC4's key schedule made the early key stream bytes predictable from that. The design of the protocol was the primary fault, although RC4's key schedule made the final step easy.

6. What is RC4's current status? Prohibited in Transport Layer Security by RFC 7465 of February 2015, which requires clients not to offer RC4 cipher suites and servers not to select one. It should not be used anywhere.

7. Trace the first step of RC4's key schedule for the key 1 2 3. The array starts as the identity, with S[n] equal to n, and j starts at 0. With i at 0, j becomes (0 + S[0] + key[0]) mod 256, that is (0 + 0 + 1) mod 256, which is 1; then S[0] and S[1] are swapped, so S[0] becomes 1 and S[1] becomes 0.

Contents This chapter on its own page

munotes.in178

Chapter Thirty-One

The Arithmetic of Public Keys: Modular Arithmetic and the gcd

Syllabus topic Module 1, row 1, Connection with Other Courses: "Discrete Mathematics (number theory and cryptographic foundations)"

In one line

Work with remainders instead of numbers. Public-key cryptography is arithmetic in a finite set of remainders, and two algorithms do almost all the work: Euclid's, which finds a greatest common divisor, and its extended form, which finds a multiplicative inverse.

In the wording a student can write in an examination: two integers a and b are congruent modulo n, written a is congruent to b mod n, when they leave the same remainder on division by n, equivalently when n divides a - b. The integers modulo n form a set of n residue classes, and addition, subtraction and multiplication are well defined on them. Division is not: a has a multiplicative inverse modulo n if and only if gcd(a, n) is 1, and the inverse is then found by the extended Euclidean algorithm.

Why the subject needs this

Every public-key algorithm on this syllabus is a statement about remainders.

RSA encrypts by computing M to the power e modulo n, and decrypts by computing the result to the power d modulo n. It works because d is the inverse of e modulo phi(n), which is a fact about inverses. Diffie-Hellman computes g to the power a modulo p. The Digital Signature Algorithm needs an inverse modulo q.

So a student who does not know what an inverse modulo n is cannot say why RSA decrypts, and cannot compute d when a question gives them p, q and e. Which questions do, on every paper.

Congruence and the residue classes

Fixing n, every integer leaves one of n possible remainders, from 0 to n - 1, and those n classes are the whole arithmetic. Two numbers in the same class behave identically for every purpose, which is what makes the arithmetic finite and therefore computable.

Three properties make it usable, and all three are worth stating because a question may ask for them.

Addition and multiplication respect the classes. If a and a' are congruent, and b and b' are congruent, then a + b and a' + b' are congruent, and so are a b and a' b'. So you may reduce at any point, which is what keeps the numbers small in a real computation.

Every class has an additive inverse. The class of -a is the class of n - a, so subtraction always works.

Not every class has a multiplicative inverse. This is the one that matters, and the tables below show it.

The tables, and the reason

# Modular arithmetic, Euclid's algorithm, and the extended algorithm that
# finds an inverse. Everything RSA and Diffie-Hellman rest on.

def gcd(a, b):
    while b:
        a, b = b, a % b
    return a

def gcd_trace(a, b):
    rows = []
    while b:
        q, r = divmod(a, b)
        rows.append((a, b, q, r))
        a, b = b, r
    return rows, a

def egcd(a, b):
    """Returns (g, x, y) with a*x + b*y = g = gcd(a, b)."""
    old_r, r = a, b
    old_s, s = 1, 0
    old_t, t = 0, 1
    while r:
        q = old_r // r
        old_r, r = r, old_r - q * r
        old_s, s = s, old_s - q * s
        old_t, t = t, old_t - q * t
    return old_r, old_s, old_t

def modinv(a, m):
    g, x, _ = egcd(a % m, m)
    return None if g != 1 else x % m

print("congruence: 17 and 5 leave the same remainder modulo 12")
for n in (17, 5, 29, -7):
    print("   %3d mod 12 = %2d" % (n, n % 12))
print("   so 17, 5, 29 and -7 are all congruent modulo 12, and in Python the")
print("   remainder of a negative number is already brought into range.")
print()

print("the residue classes modulo 7, and the multiplication table:")
print("   x  " + "".join("%4d" % b for b in range(7)))
for a in range(7):
    print("   %d  " % a + "".join("%4d" % ((a * b) % 7) for b in range(7)))
print("   every non-zero row is a permutation of 1 to 6, because 7 is prime,")
print("   so every non-zero element has a multiplicative inverse modulo 7.")
print()

print("modulo 12 it is not so:")
print("   x  " + "".join("%4d" % b for b in range(12)))
for a in (2, 3, 5):
    print("   %d  " % a + "".join("%4d" % ((a * b) % 12) for b in range(12)))
print("   row 5 is a permutation and rows 2 and 3 are not, because gcd(5, 12)")
print("   is 1 while gcd(2, 12) is 2 and gcd(3, 12) is 3.")
print()

print("Euclid's algorithm on 1180 and 482:")
rows, g = gcd_trace(1180, 482)
print("      a     b    q     r")
for a, b, q, r in rows:
    print("   %4d  %4d  %3d  %4d" % (a, b, q, r))
print("   gcd =", g, " and it took", len(rows), "divisions")
print()

print("the extended algorithm gives the inverse as well:")
for a, m in ((7, 160), (17, 3120), (3, 26), (2, 26)):
    g, x, y = egcd(a, m)
    inv = modinv(a, m)
    print("   %4d and %4d: gcd %2d, and %4d * %5d + %4d * %5d = %d"
          % (a, m, g, a, x, m, y, a * x + m * y))
    if inv is None:
        print("        no inverse, because the gcd is not 1")
    else:
        print("        inverse of %d mod %d is %d, and %d * %d mod %d = %d"
              % (a, m, inv, a, inv, m, (a * inv) % m))
print()

print("why an inverse needs gcd 1: if gcd(a, m) = d > 1 then a*x mod m is always")
print("a multiple of d, so it can never be 1.")
print("   2 * x mod 26 for x = 0 to 12:",
      ", ".join(str((2 * x) % 26) for x in range(13)))
print("   every value is even, so 1 never appears.")
munotes.in179

The Arithmetic of Public Keys: Modular Arithmetic and the gcd

congruence: 17 and 5 leave the same remainder modulo 12
    17 mod 12 =  5
     5 mod 12 =  5
    29 mod 12 =  5
    -7 mod 12 =  5
   so 17, 5, 29 and -7 are all congruent modulo 12, and in Python the
   remainder of a negative number is already brought into range.

the residue classes modulo 7, and the multiplication table:
   x     0   1   2   3   4   5   6
   0     0   0   0   0   0   0   0
   1     0   1   2   3   4   5   6
   2     0   2   4   6   1   3   5
   3     0   3   6   2   5   1   4
   4     0   4   1   5   2   6   3
   5     0   5   3   1   6   4   2
   6     0   6   5   4   3   2   1
   every non-zero row is a permutation of 1 to 6, because 7 is prime,
   so every non-zero element has a multiplicative inverse modulo 7.

modulo 12 it is not so:
   x     0   1   2   3   4   5   6   7   8   9  10  11
   2     0   2   4   6   8  10   0   2   4   6   8  10
   3     0   3   6   9   0   3   6   9   0   3   6   9
   5     0   5  10   3   8   1   6  11   4   9   2   7
   row 5 is a permutation and rows 2 and 3 are not, because gcd(5, 12)
   is 1 while gcd(2, 12) is 2 and gcd(3, 12) is 3.

Euclid's algorithm on 1180 and 482:
      a     b    q     r
   1180   482    2   216
    482   216    2    50
    216    50    4    16
     50    16    3     2
     16     2    8     0
   gcd = 2  and it took 5 divisions

the extended algorithm gives the inverse as well:
      7 and  160: gcd  1, and    7 *    23 +  160 *    -1 = 1
        inverse of 7 mod 160 is 23, and 7 * 23 mod 160 = 1
     17 and 3120: gcd  1, and   17 *  -367 + 3120 *     2 = 1
        inverse of 17 mod 3120 is 2753, and 17 * 2753 mod 3120 = 1
      3 and   26: gcd  1, and    3 *     9 +   26 *    -1 = 1
        inverse of 3 mod 26 is 9, and 3 * 9 mod 26 = 1
      2 and   26: gcd  2, and    2 *     1 +   26 *     0 = 2
        no inverse, because the gcd is not 1

why an inverse needs gcd 1: if gcd(a, m) = d > 1 then a*x mod m is always
a multiple of d, so it can never be 1.
   2 * x mod 26 for x = 0 to 12: 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24
   every value is even, so 1 never appears.
munotes.in180

The Arithmetic of Public Keys: Modular Arithmetic and the gcd

Read five things out of that run.

munotes.in181

The Arithmetic of Public Keys: Modular Arithmetic and the gcd

A negative remainder is brought into range. Minus 7 modulo 12 is 5, not minus 7. This is the arithmetic slip that ruins hand-worked decryptions, and Python does it correctly by default, which is worth knowing when checking your own work.

Modulo 7 every non-zero row of the multiplication table is a permutation of 1 to 6. Row 3, for instance, is 3, 6, 2, 5, 1, 4. Since 1 appears in every row, every non-zero element has an inverse, and that is what it means for the integers modulo a prime to form a field.

Modulo 12 it fails, and you can see exactly where. Row 5 is a permutation, so 5 has an inverse. Row 2 is 0, 2, 4, 6, 8, 10, 0, 2, 4, 6, 8, 10: it repeats after six entries and never reaches 1, so 2 has no inverse. Row 3 is the same story with period 4. The discriminator is the gcd: 5 shares no factor with 12, while 2 and 3 do.

Euclid's algorithm took five divisions on numbers of four digits. That is the point of it: the numbers fall fast, because each step replaces the pair by the smaller number and the remainder. For numbers of 1,024 bits it takes a few thousand steps, not 2 to the power 1,024.

The extended algorithm prints its own proof. For 7 and 160 it produced the identity with coefficients 23 and minus 1, so 7 times 23 plus 160 times minus 1 comes to one, and 23 is therefore the inverse of 7 modulo 160, which the run then checks by multiplying. That pair of numbers will reappear in the RSA chapter as e and d.

Euclid's algorithm, by hand

The algorithm rests on one observation: gcd(a, b) equals gcd(b, a mod b). Any common divisor of a and b divides a - qb for any q, and any common divisor of b and a - qb divides a, so the two pairs have the same common divisors and the same greatest one. Repeating shrinks the numbers until one is zero, and the other is the answer.

munotes.in182

The Arithmetic of Public Keys: Modular Arithmetic and the gcd

Find gcd(1180, 482). Divide and keep the remainder each time.

1180 divided by 482 is 2 remainder 216. 482 divided by 216 is 2 remainder 50. 216 divided by 50 is 4 remainder 16. 50 divided by 16 is 3 remainder 2. 16 divided by 2 is 8 remainder 0.

The last non-zero remainder is 2, and that is the gcd. The run above prints exactly those five rows.

The extended algorithm, and why it gives an inverse

The extended algorithm keeps track of how each remainder was built out of the original two numbers, so that at the end it can write

a x + n y = gcd(a, n)

which is called Bezout's identity. Now suppose gcd(a, n) is 1. Then a x + n y = 1, and reading that modulo n the second term vanishes, leaving a x congruent to 1 modulo n. So x is the inverse of a modulo n, and if x came out negative you add n.

Find the inverse of 3 modulo 26, which the Hill chapter needed. The run gives coefficients 9 and minus 1, so 3 times 9 plus 26 times minus 1 comes to one, the inverse is 9, and 3 times 9 is 27, which is 1 modulo 26.

Find the inverse of 2 modulo 26. The algorithm returns a gcd of 2, not 1, so there is no inverse, which is exactly the Hill cipher's unusable key.

A worked example a question will actually set

"Given p of 47 and q of 71 and e of 79, find d."

Step 1: the modulus and the totient. n is 47 times 71, which is 3,337. And phi(n) is 46 times 70, which is 3,220.

Step 2: check e is usable. gcd(79, 3220). 3220 divided by 79 is 40 remainder 60. 79 divided by 60 is 1 remainder 19. 60 divided by 19 is 3 remainder 3. 19 divided by 3 is 6 remainder 1. 3 divided by 1 is 3 remainder 0. The gcd is 1, so e is usable.

Step 3: run the extended algorithm backwards. From the divisions above, substituting each remainder in turn: 1 is 19 minus 6 times 3; 3 is 60 minus 3 times 19, so 1 is 19 minus 6 times (60 minus 3 times 19), which is 19 times 19 minus 6 times 60; 19 is 79 minus 60, so 1 is 19 times (79 minus 60) minus 6 times 60, which is 19 times 79 minus 25 times 60; and 60 is 3220 minus 40 times 79, so 1 is 19 times 79 minus 25 times (3220 minus 40 times 79), which is 1,019 times 79 less 25 times 3,220.

munotes.in183

The Arithmetic of Public Keys: Modular Arithmetic and the gcd

Step 4: read off d. d is 1019.

Step 5: check. 79 times 1019 is 80,501. And 3220 times 25 is 80,500. So 80,501 is 1 more than a multiple of 3,220, which means 79 times 1019 is congruent to 1 modulo 3,220. Correct.

The step that carries the marks. Step 3, the back-substitution, is the only difficult part and it is entirely mechanical: work upwards through the divisions, replacing each remainder by its expression. Write out the divisions first, then substitute. And always do step 5.

Distinctions that carry marks

gcdlcm
Isthe largest number dividing boththe smallest number both divide
Found byEuclid's algorithma * b / gcd(a, b)
Used here forchecking a key is usable, finding an inversenot much
Euclid's algorithmThe extended Euclidean algorithm
Returnsthe gcdthe gcd and the Bezout coefficients x, y
Gives an inversenoyes, when the gcd is 1
Costa few thousand steps for 1,024-bit numbersthe same
Integers modulo a prime pIntegers modulo a composite n
Every non-zero element has an inverseyesno
Structurea fielda ring
Multiplication table rowsall non-zero rows are permutationsonly rows coprime to n are
Examplemodulo 7modulo 12

What beginners get wrong here

Writing a negative remainder. Minus 2 modulo 26 is 24. Bring every intermediate value into the range 0 to n - 1.

Assuming division works. You may not divide modulo n; you multiply by an inverse, and the inverse may not exist.

Forgetting to check the gcd before looking for an inverse. If gcd(a, n) is not 1 there is nothing to find, and the algorithm tells you so.

Confusing phi(n) with n when finding d. d is the inverse of e modulo phi(n), never modulo n. It is the commonest error in an RSA question.

Not checking the answer. e times d modulo phi(n) must be 1. It takes one multiplication and it catches every slip.

Quick revision

  • a congruent to b mod n means n divides a - b; there are n residue classes.
  • Addition, subtraction and multiplication are well defined on the classes, so you may reduce at any point.
  • a has an inverse modulo n if and only if gcd(a, n) = 1. Modulo a prime, every non-zero element has one, so the integers modulo a prime form a field.
  • Euclid: gcd(a, b) = gcd(b, a mod b), repeated until the remainder is 0. gcd(1180, 482) is 2 in five divisions.
  • Extended Euclid gives a x + n y = gcd(a, n), Bezout's identity. When the gcd is 1, x is the inverse of a modulo n, brought into range by adding n if negative.
  • Inverse of 7 modulo 160 is 23; of 3 modulo 26 is 9; 2 modulo 26 has none.
  • d is the inverse of e modulo phi(n), not modulo n. Always check e d mod phi(n) = 1.
munotes.in184

The Arithmetic of Public Keys: Modular Arithmetic and the gcd

Test yourself

1. Define congruence modulo n and say how many residue classes there are. a is congruent to b modulo n when both leave the same remainder on division by n, equivalently when n divides a - b. There are n residue classes, represented by the remainders 0 to n - 1.

2. When does an integer have a multiplicative inverse modulo n, and why? Exactly when gcd(a, n) is 1. If the gcd is d greater than 1 then every multiple of a modulo n is a multiple of d, so 1 can never be reached; and when the gcd is 1, Bezout's identity gives integers x and y with a x + n y = 1, so a x is congruent to 1 modulo n and x is the inverse.

3. Find gcd(1180, 482) by Euclid's algorithm, showing the divisions. 1180 divided by 482 is 2 remainder 216; 482 by 216 is 2 remainder 50; 216 by 50 is 4 remainder 16; 50 by 16 is 3 remainder 2; 16 by 2 is 8 remainder 0. The last non-zero remainder is 2, so the gcd is 2.

4. Find the inverse of 7 modulo 160. The extended algorithm returns the coefficients 23 and minus 1, so 7 times 23 plus 160 times minus 1 comes to one, and the inverse is 23. Checking, 7 times 23 is 161, which is 1 modulo 160.

5. Given p of 47, q of 71 and e of 79, find d. n is 3,337 and phi(n) is 46 times 70, which is 3,220. The extended Euclidean algorithm on 79 and 3,220 returns the coefficients 1,019 and minus 25, so 1,019 times 79 less 25 times 3,220 comes to one, and d is 1,019. Checking, 79 times 1,019 is 80,501, which is one more than 25 times 3,220, so the product is 1 modulo 3,220.

6. Why do the integers modulo 7 form a field while those modulo 12 do not? Because 7 is prime, so every non-zero residue is coprime to 7 and therefore invertible; the multiplication table's non-zero rows are all permutations of 1 to 6. Modulo 12 the residues 2, 3, 4, 6, 8, 9 and 10 share a factor with 12 and have no inverse, so division is not generally possible.

munotes.in185

The Arithmetic of Public Keys: Modular Arithmetic and the gcd

7. What is Bezout's identity and what is it used for here? That for any integers a and n there exist integers x and y with a x + n y = gcd(a, n), and the extended Euclidean algorithm computes them. When the gcd is 1 it delivers the multiplicative inverse of a modulo n, which is how RSA's private exponent d is obtained from e and phi(n).

Contents This chapter on its own page

munotes.in186

Chapter Thirty-Two

Fermat, Euler and the Totient Function

Syllabus topic Module 1, row 1, Connection with Other Courses: "Discrete Mathematics (number theory and cryptographic foundations)"

In one line

Euler's totient counts how many numbers below n share no factor with it, and Euler's theorem says that raising anything coprime to n to that power gives 1. RSA decrypts because of that one sentence.

In the wording a student can write in an examination: Euler's totient function phi(n) is the number of positive integers less than n that are relatively prime to n. For a prime p, phi(p) is p - 1. For a product of two distinct primes, phi(pq) is (p - 1)(q - 1). Fermat's little theorem states that if p is prime and a is not divisible by p, then a to the power p - 1 is congruent to 1 modulo p. Euler's theorem generalises it: for any n and any a with gcd(a, n) equal to 1, a to the power phi(n) is congruent to 1 modulo n.

Why these two theorems and no others

Because RSA's correctness is exactly Euler's theorem, and nothing else in the algorithm needs proving.

RSA picks e and d so that e d is congruent to 1 modulo phi(n). That means e d equals 1 + k phi(n) for some whole number k. Encrypting then decrypting computes M to the power e d, which is M to the power 1 + k phi(n), which is M times (M to the power phi(n)) to the power k. And Euler's theorem says the bracket is 1. So the result is M. That is the whole proof, and it is four lines, and it is set.

Fermat's little theorem is the special case for a prime modulus, and it earns its own place for two reasons: it is the basis of primality testing, and its failure modes are instructive.

The totient, computed two ways

For a general n, factorise it: if n is the product of prime powers p1 to the power k1 times p2 to the power k2 and so on, then

phi(n) = n (1 - 1/p1) (1 - 1/p2) ...

The two cases that matter here follow at once. For a prime p, every one of 1 to p - 1 is coprime to p, so phi(p) is p - 1. For a product of two distinct primes, phi(pq) is pq(1 - 1/p)(1 - 1/q), which is (p - 1)(q - 1).

And that is the trapdoor. Anybody who knows n can compute n. Only somebody who knows the factorisation p and q can compute phi(n), and therefore only they can compute d from e. RSA's security is precisely the difficulty of getting from n to phi(n), which is the difficulty of factoring.

munotes.in187

Fermat, Euler and the Totient Function

The run

# Fermat, Euler, and the totient function.

def totient(n):
    """Euler's totient by trial division: how many of 1 to n are coprime to n."""
    result, m, p = n, n, 2
    while p * p <= m:
        if m % p == 0:
            while m % p == 0:
                m //= p
            result -= result // p
        p += 1
    if m > 1:
        result -= result // m
    return result

def coprime_to(n):
    from math import gcd
    return [a for a in range(1, n) if gcd(a, n) == 1]

print("the totient function counted directly, and by the formula:")
for n in (7, 10, 11, 12, 21, 26, 35, 187):
    direct = len(coprime_to(n)) if n < 200 else None
    print("   phi(%3d) = %3d   by formula %3d   agree: %s"
          % (n, direct, totient(n), direct == totient(n)))
print()

print("for a prime p, phi(p) = p - 1; the numbers coprime to 11 are")
print("  ", coprime_to(11))
print("for a product of two distinct primes, phi(pq) = (p-1)(q-1):")
for p, q in ((3, 7), (11, 17), (7, 11)):
    print("   phi(%d * %d) = phi(%d) = %d, and (%d-1)(%d-1) = %d"
          % (p, q, p * q, totient(p * q), p, q, (p - 1) * (q - 1)))
print()

print("Fermat's little theorem: for p prime and a not divisible by p,")
print("a to the power (p - 1) is congruent to 1 modulo p.")
for a, p in ((2, 7), (3, 11), (5, 13), (10, 17)):
    print("   %2d to the power %2d mod %2d = %d" % (a, p - 1, p, pow(a, p - 1, p)))
print()

print("Euler's theorem generalises it to any modulus:")
print("a to the power phi(n) is congruent to 1 modulo n, whenever gcd(a, n) = 1.")
for a, n in ((3, 10), (2, 11), (7, 187), (5, 26)):
    print("   %d to the power phi(%3d) = %d to the power %3d mod %3d = %d"
          % (a, n, a, totient(n), n, pow(a, totient(n), n)))
print()

print("and what the theorems do NOT say. 561 passes Fermat's test for base 2:")
print("   2 to the power 560 mod 561 =", pow(2, 560, 561))
print("   but 561 =", " * ".join(str(f) for f in (3, 11, 17)), "and so is composite.")
print("   A number that passes for every base coprime to it is a CARMICHAEL")
print("   number. The first four are 561, 1105, 1729 and 2465.")
from math import gcd
for n in (561, 1105, 1729, 2465):
    bad = [a for a in range(2, n) if gcd(a, n) == 1 and pow(a, n - 1, n) != 1]
    print("   %4d: bases coprime to it that FAIL Fermat's test: %d" % (n, len(bad)))
print()
print("so Fermat's test can prove a number COMPOSITE and can never prove one PRIME.")
munotes.in188

Fermat, Euler and the Totient Function

the totient function counted directly, and by the formula:
   phi(  7) =   6   by formula   6   agree: True
   phi( 10) =   4   by formula   4   agree: True
   phi( 11) =  10   by formula  10   agree: True
   phi( 12) =   4   by formula   4   agree: True
   phi( 21) =  12   by formula  12   agree: True
   phi( 26) =  12   by formula  12   agree: True
   phi( 35) =  24   by formula  24   agree: True
   phi(187) = 160   by formula 160   agree: True

for a prime p, phi(p) = p - 1; the numbers coprime to 11 are
   [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for a product of two distinct primes, phi(pq) = (p-1)(q-1):
   phi(3 * 7) = phi(21) = 12, and (3-1)(7-1) = 12
   phi(11 * 17) = phi(187) = 160, and (11-1)(17-1) = 160
   phi(7 * 11) = phi(77) = 60, and (7-1)(11-1) = 60

Fermat's little theorem: for p prime and a not divisible by p,
a to the power (p - 1) is congruent to 1 modulo p.
    2 to the power  6 mod  7 = 1
    3 to the power 10 mod 11 = 1
    5 to the power 12 mod 13 = 1
   10 to the power 16 mod 17 = 1

Euler's theorem generalises it to any modulus:
a to the power phi(n) is congruent to 1 modulo n, whenever gcd(a, n) = 1.
   3 to the power phi( 10) = 3 to the power   4 mod  10 = 1
   2 to the power phi( 11) = 2 to the power  10 mod  11 = 1
   7 to the power phi(187) = 7 to the power 160 mod 187 = 1
   5 to the power phi( 26) = 5 to the power  12 mod  26 = 1

and what the theorems do NOT say. 561 passes Fermat's test for base 2:
   2 to the power 560 mod 561 = 1
   but 561 = 3 * 11 * 17 and so is composite.
   A number that passes for every base coprime to it is a CARMICHAEL
   number. The first four are 561, 1105, 1729 and 2465.
    561: bases coprime to it that FAIL Fermat's test: 0
   1105: bases coprime to it that FAIL Fermat's test: 0
   1729: bases coprime to it that FAIL Fermat's test: 0
   2465: bases coprime to it that FAIL Fermat's test: 0

so Fermat's test can prove a number COMPOSITE and can never prove one PRIME.

Read five things out of that run.

The formula and the count agree for every value tested. phi(187) is 160 both ways, phi(35) is 24, phi(26) is 12. The formula is not being taken on trust.

munotes.in189

Fermat, Euler and the Totient Function

phi(11 17) is 160, which is 10 16. That is the number the RSA chapter will use, and e of 7 with d of 23 came out of the previous chapter's extended Euclid on exactly 160.

Fermat's theorem holds on every instance shown, and Euler's holds on every instance including 7 to the power 160 modulo 187, which is 1.

561 passes Fermat's test for base 2 and is 3 times 11 times 17. So a number can satisfy a to the power n - 1 congruent to 1 modulo n without being prime. Such a number is a pseudoprime to that base.

And the exhaustive check is the important line. For each of 561, 1105, 1729 and 2465, the program counted the bases coprime to n that fail Fermat's test, and the count was 0 in every case. These are Carmichael numbers: composite numbers that pass Fermat's test for every base coprime to them. So no amount of trying more bases will detect them, and Fermat's test can prove a number composite but can never prove one prime. That is why the next chapter is about Miller and Rabin.

The proof of RSA, written out

Because it is set, and because it is short.

Given. n is pq with p and q distinct primes. e and d satisfy e d congruent to 1 modulo phi(n). The message M satisfies 0 <= M < n.

Step 1. Since e d is congruent to 1 modulo phi(n), there is a whole number k with e d = 1 + k phi(n).

Step 2. Decrypting the encryption computes (M to the power e) to the power d, which is M to the power e d, which is M to the power 1 + k phi(n).

Step 3. That equals M times (M to the power phi(n)) to the power k.

Step 4. If gcd(M, n) is 1, Euler's theorem says M to the power phi(n) is congruent to 1 modulo n, so the whole expression is congruent to M times 1 to the power k, which is M. Since M is less than n, the congruence pins it exactly.

Step 5, the case the textbooks skip. What if gcd(M, n) is not 1, so that M is a multiple of p or of q? The theorem as stated does not apply. It still works, and the reason is the Chinese Remainder Theorem: the congruence holds separately modulo p and modulo q (by Fermat's little theorem in each), and a number determined modulo p and modulo q is determined modulo pq. So RSA decrypts correctly for every message, not only for those coprime to n. A complete answer mentions this; most do not.

munotes.in190

Fermat, Euler and the Totient Function

Distinctions that carry marks

Fermat's little theoremEuler's theorem
Modulusa prime pany n
Conditiona not divisible by pgcd(a, n) = 1
Statementa to the power p - 1 is 1 mod pa to the power phi(n) is 1 mod n
Relationshipthe special case where phi(p) = p - 1the generalisation
Used forprimality testingproving RSA works
PrimePseudoprime to base aCarmichael number
Passes Fermat for base aalwaysyes, but it is compositeyes, for every base coprime to it
Detected by more basesnot applicableoftennever
Smallest examples2, 3, 5341 for base 2561, 1105, 1729, 2465
nphi(n)
Computable from n aloneyesno
Needs the factorisationnoyes
Public in RSAyesno
This asymmetry isthe trapdoor

What beginners get wrong here

Saying phi(n) is n - 1 for composite n. It is n - 1 only for a prime. For n equal to pq it is (p - 1)(q - 1), which is much smaller.

Computing phi of a prime power wrongly. phi(p to the power k) is p to the power k minus p to the power k - 1, not p - 1. So phi(9) is 6, not 2.

Thinking Fermat's test proves primality. It cannot, and the Carmichael numbers are why: 561, 1105, 1729 and 2465 pass for every base coprime to them, and the program checked every one.

Stopping the RSA proof at the coprime case. It holds for all messages, by the Chinese Remainder Theorem, and saying so is worth a mark.

Confusing which of n and phi(n) is public. n is published; phi(n) is as secret as the primes, because it is equivalent to them.

Quick revision

  • phi(n) counts the integers below n coprime to it. phi(p) = p - 1; phi(pq) = (p - 1)(q - 1); phi(p to the power k) = p to the power k minus p to the power k - 1.
  • Verified: phi(187) = 160, phi(35) = 24, phi(26) = 12, both by counting and by formula.
  • Fermat: p prime, a not divisible by p, then a to the power p - 1 is 1 mod p.
  • Euler: gcd(a, n) = 1, then a to the power phi(n) is 1 mod n. Fermat is the case n = p.
  • RSA's proof: e d = 1 + k phi(n), so M to the power e d is M times (M to the power phi(n)) to the power k, which is M. It also holds when gcd(M, n) is not 1, by the Chinese Remainder Theorem.
  • Fermat's test cannot prove primality. The Carmichael numbers 561, 1105, 1729 and 2465 pass for every base coprime to them, and the program counted the failures at 0.
  • The trapdoor: n is computable by anybody, phi(n) only by somebody who knows p and q.
munotes.in191

Fermat, Euler and the Totient Function

Test yourself

1. Define Euler's totient function and give its value for a prime and for a product of two primes. phi(n) is the number of positive integers less than n that are relatively prime to n. For a prime p it is p - 1, because every smaller positive integer is coprime to p. For a product of two distinct primes it is (p - 1)(q - 1).

2. State Fermat's little theorem and Euler's theorem, and give the relationship between them. Fermat: if p is prime and a is not divisible by p, then a to the power p - 1 is congruent to 1 modulo p. Euler: if gcd(a, n) is 1 then a to the power phi(n) is congruent to 1 modulo n. Fermat's theorem is the special case of Euler's with n prime, since phi(p) is p - 1.

3. Compute phi(35), phi(9) and phi(187). phi(35) is phi(5 7), which is 4 times 6, that is 24. phi(9) is phi(3 to the power 2), which is 9 minus 3, that is 6. phi(187) is phi(11 17), which is 10 times 16, that is 160.

4. Prove that RSA decryption recovers the plaintext. Since e d is congruent to 1 modulo phi(n) there is a whole number k with e d = 1 + k phi(n). Then (M to the power e) to the power d is M to the power 1 + k phi(n), which is M times (M to the power phi(n)) to the power k. By Euler's theorem the bracket is congruent to 1 modulo n when gcd(M, n) is 1, so the result is congruent to M, and since M is less than n it equals M. When gcd(M, n) is not 1 the result still holds, because the congruence is valid separately modulo p and modulo q by Fermat's little theorem, and the Chinese Remainder Theorem then fixes it modulo pq.

5. What is a Carmichael number, and what does its existence prove? A composite number that passes Fermat's test for every base coprime to it; the smallest are 561, 1105, 1729 and 2465. Its existence proves that Fermat's test can never establish primality, however many bases are tried, because for these numbers no coprime base fails. The chapter verifies this exhaustively: the count of failing coprime bases is zero for all four.

munotes.in192

Fermat, Euler and the Totient Function

6. Why is phi(n) secret in RSA when n is public? Because computing phi(n) from n requires the factorisation of n. Anybody can publish n; only the holder of p and q can compute (p - 1)(q - 1) and hence obtain d as the inverse of e. Knowing phi(n) is equivalent to knowing the factors, so it is as secret as they are, and that asymmetry is the trapdoor the whole scheme rests on.

7. 341 passes Fermat's test for base 2. What does that tell you, and what would you do next? That 341 is a pseudoprime to base 2, so the test gives no information about its primality. The next step is to try another base: 341 is 11 times 31, and testing base 3 gives a value other than 1, which proves it composite. That works because 341 is not a Carmichael number; for a Carmichael number no base would help, which is the argument for Miller and Rabin.

Contents This chapter on its own page

munotes.in193

Chapter Thirty-Three

Fast Modular Exponentiation, and Testing a Number for Primality

Syllabus topic Module 1, row 1, Connection with Other Courses: "Discrete Mathematics (number theory and cryptographic foundations)"

In one line

Square and multiply your way up the bits of the exponent, and never let the numbers grow. That turns an exponentiation with a 1,024-bit exponent from impossible into a few thousand multiplications, and a companion test tells you whether a number is prime without factoring it.

In the wording a student can write in an examination: modular exponentiation by the square and multiply method computes a to the power b modulo n in a number of steps proportional to the number of bits of b, rather than to b itself. Write b in binary; scan the bits from the most significant; at each bit square the running result modulo n, and where the bit is 1 also multiply by a modulo n. Primality testing by the Miller and Rabin algorithm decides whether a number is prime with high confidence without factoring it, by strengthening Fermat's test with the observation that in a field the only square roots of 1 are 1 and minus 1.

Why both are needed before RSA

Without fast exponentiation there is no RSA. Encrypting with e of 65,537 means computing M to the power 65,537. Done naively that is 65,536 multiplications of numbers hundreds of digits long, and decryption with a 1,024-bit d would be 2 to the power 1,024 of them, which is not a number of operations anybody will perform. Square and multiply brings both within reach.

Without primality testing there is no RSA key. Key generation needs two primes of 1,024 bits or more. There is no table of them; they have to be found, and found means: pick a random odd number of the right size and test it, repeatedly, until one is prime. So the cost of generating an RSA key is the cost of the primality test times the number of candidates.

Square and multiply

The identity is simply that a to the power 2k is (a to the power k) squared, and a to the power 2k + 1 is a times that. Reading the exponent's bits from the top and applying that rule at each step gives the algorithm.

Two things keep it practical. Reduce modulo n at every step, so no intermediate value exceeds n squared. And count the steps by bits: an exponent of k bits takes k squarings and at most k multiplications.

The run

# Square and multiply, and Miller and Rabin. The two algorithms without which
# RSA is arithmetic nobody can perform.

def modexp(base, exp, mod):
    result = 1
    base %= mod
    for bit in bin(exp)[2:]:
        result = (result * result) % mod
        if bit == "1":
            result = (result * base) % mod
    return result

def modexp_trace(base, exp, mod):
    rows, result = [], 1
    for i, bit in enumerate(bin(exp)[2:], 1):
        squared = (result * result) % mod
        result = (squared * base) % mod if bit == "1" else squared
        rows.append((i, bit, squared, result))
    return rows, result

print("7 to the power 560 mod 561, by squaring and multiplying:")
print("   560 in binary is", bin(560)[2:], "so there are", len(bin(560)[2:]),
      "steps, not 560")
rows, answer = modexp_trace(7, 560, 561)
print("   step  bit   after squaring   after multiplying")
for i, bit, sq, res in rows:
    print("   %4d   %s   %14d   %17d" % (i, bit, sq, res))
print("   answer:", answer, " and Python's own pow agrees:", pow(7, 560, 561))
print()

print("the saving, counted in multiplications:")
for e in (560, 65537, 2 ** 1024):
    naive = e - 1
    fast = 2 * len(bin(e)) - 2
    print("   exponent of %d bits: naive about %s multiplications, fast about %d"
          % (e.bit_length(), format(naive, ",") if e < 10 ** 12 else "2 to the power %d" % e.bit_length(), fast))
print()

def miller_rabin(n, bases=(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37)):
    if n < 2:
        return False
    for p in bases:
        if n % p == 0:
            return n == p
    d, r = n - 1, 0
    while d % 2 == 0:
        d //= 2
        r += 1
    for a in bases:
        x = pow(a, d, n)
        if x in (1, n - 1):
            continue
        for _ in range(r - 1):
            x = (x * x) % n
            if x == n - 1:
                break
        else:
            return False
    return True

print("Miller and Rabin sees what Fermat's test cannot:")
for n in (561, 1105, 1729, 2465, 104729, 4294967297):
    print("   %12d  prime by Miller and Rabin: %-5s   Fermat base 2 says: %s"
          % (n, miller_rabin(n), "prime" if pow(2, n - 1, n) == 1 else "composite"))
print("   4294967297 is the Fermat number 2 to the power 32 plus 1, which Euler")
print("   factorised in 1732 as 641 times 6700417:", 641 * 6700417 == 4294967297)
print()

print("how many primes there are to choose from, near the sizes RSA uses:")
import math
for bits in (512, 1024, 2048):
    density = 1 / (bits * math.log(2))
    print("   near 2 to the power %4d, about 1 in %d odd numbers is prime,"
          % (bits, int(1 / density / 2) * 2 // 2 * 2))
    print("        so about %d candidates are tried to find one" % int(bits * math.log(2) / 2))
print()

print("finding a 512-bit prime, for real:")
import random
random.seed(3)
tries = 0
while True:
    tries += 1
    cand = random.getrandbits(512) | (1 << 511) | 1
    if miller_rabin(cand):
        break
print("   candidates tested:", tries)
print("   the prime found has", cand.bit_length(), "bits and ends in",
      str(cand)[-6:])
print("   it is prime by Miller and Rabin:", miller_rabin(cand))
munotes.in194

Fast Modular Exponentiation, and Testing a Number for Primality

7 to the power 560 mod 561, by squaring and multiplying:
   560 in binary is 1000110000 so there are 10 steps, not 560
   step  bit   after squaring   after multiplying
      1   1                1                   7
      2   0               49                  49
      3   0              157                 157
      4   0              526                 526
      5   1              103                 160
      6   1              355                 241
      7   0              298                 298
      8   0              166                 166
      9   0               67                  67
     10   0                1                   1
   answer: 1  and Python's own pow agrees: 1

the saving, counted in multiplications:
   exponent of 10 bits: naive about 559 multiplications, fast about 22
   exponent of 17 bits: naive about 65,536 multiplications, fast about 36
   exponent of 1025 bits: naive about 2 to the power 1025 multiplications, fast about 2052

Miller and Rabin sees what Fermat's test cannot:
            561  prime by Miller and Rabin: False   Fermat base 2 says: prime
           1105  prime by Miller and Rabin: False   Fermat base 2 says: prime
           1729  prime by Miller and Rabin: False   Fermat base 2 says: prime
           2465  prime by Miller and Rabin: False   Fermat base 2 says: prime
         104729  prime by Miller and Rabin: True    Fermat base 2 says: prime
     4294967297  prime by Miller and Rabin: False   Fermat base 2 says: prime
   4294967297 is the Fermat number 2 to the power 32 plus 1, which Euler
   factorised in 1732 as 641 times 6700417: True

how many primes there are to choose from, near the sizes RSA uses:
   near 2 to the power  512, about 1 in 354 odd numbers is prime,
        so about 177 candidates are tried to find one
   near 2 to the power 1024, about 1 in 708 odd numbers is prime,
        so about 354 candidates are tried to find one
   near 2 to the power 2048, about 1 in 1418 odd numbers is prime,
        so about 709 candidates are tried to find one

finding a 512-bit prime, for real:
   candidates tested: 107
   the prime found has 512 bits and ends in 446633
   it is prime by Miller and Rabin: True
munotes.in195

Fast Modular Exponentiation, and Testing a Number for Primality

Read six things out of that run.

560 in binary is ten bits, so the trace has ten rows rather than 560. That is the whole saving, made visible. Each row shows the value after squaring and then after multiplying if the bit was 1.

The answer is 1, and Python's own pow agrees. This is the same computation the previous chapter used to show that 561 passes Fermat's test, now performed step by step.

The saving, counted. An exponent of 17 bits, which is e of 65,537, needs about 36 multiplications instead of 65,536. An exponent of 1,025 bits needs about 2,052 instead of 2 to the power 1,025. That ratio is the reason public-key cryptography exists at all.

munotes.in196

Fast Modular Exponentiation, and Testing a Number for Primality

Miller and Rabin sees through all four Carmichael numbers, every one of which Fermat's test calls prime. So the stronger test is not a refinement; it detects a class of composites that the weaker test cannot detect at all.

It also factors nothing and still reports 4,294,967,297 composite. That number is the Fermat number 2 to the power 32 plus 1, which Euler factorised in 1732 as 641 times 6,700,417, and the program confirms the multiplication. Fermat's own test calls it prime.

A real 512-bit prime was found in 107 candidates. The chapter predicted about 177 from the prime density near 2 to the power 512, and 107 is a perfectly ordinary draw from that distribution. So generating an RSA key is a few hundred primality tests, which is a fraction of a second.

Why Miller and Rabin works

The idea is one extra observation on top of Fermat's test, and it is worth understanding because it is examinable and short.

Write n - 1 as d times 2 to the power r, with d odd. If n is prime then, by Fermat, a to the power n - 1 is 1. Now look at the chain

a to the power d, then its square, then its square, up to a to the power (n-1)

The chain ends at 1. In a field the only square roots of 1 are 1 and minus 1. So if n is prime, either the chain starts at 1, or somewhere along it a value is minus 1, that is n - 1, and everything after is 1. If neither happens, n is definitely composite: there is a square root of 1 that is neither 1 nor minus 1, which cannot occur modulo a prime.

So a failure is a proof of compositeness, and a pass is evidence of primality. For a composite n, at least three quarters of the bases witness the compositeness, so k random bases leave a chance of error below 1 in 4 to the power k. With the small prime bases the algorithm is deterministic for numbers below 3,215,031,751, which covers every number a student will meet by hand.

A worked example, by hand

Do a short exponent by hand and read a long one off the trace. The listing's ten-step trace of 7 to the power 560 is the authority for that computation, and reproducing ten modular squarings of three-digit numbers under examination conditions is a way to lose marks rather than gain them. So here is one small enough to be safe.

Compute 3 to the power 13 modulo 17.

munotes.in197

Fast Modular Exponentiation, and Testing a Number for Primality

Step 1: the exponent in binary. 13 is 1101, four bits. So four squarings and three multiplies.

Step 2: scan from the left, starting with the result at 1.

Bit 1 is 1. Square 1 to get 1, then multiply by 3: the result is 3.

Bit 2 is 1. Square 3 to get 9, then multiply by 3 to get 27, and 27 modulo 17 is 10.

Bit 3 is 0. Square 10 to get 100, and 100 modulo 17 is 15, since 17 times 5 is 85. No multiply.

Bit 4 is 1. Square 15 to get 225, and 225 modulo 17 is 4, since 17 times 13 is 221; then multiply by 3 to get 12.

Step 3: the answer is 12. Check it against the run's own method: the trace format is exactly the one above, and 3 to the power 13 is 1,594,323, which divided by 17 leaves 12, since 17 times 93,783 is 1,594,311.

Step 4: read the long trace the same way. In the printed ten-step trace of 7 to the power 560 modulo 561, the results after each step are 7, 49, 157, 526, 160, 241, 298, 166, 67 and finally 1. Four of those steps are multiplies, at bits 1, 5 and 6 of 1000110000, and the rest are squarings alone. Notice step 6: 160 squared is 25,600, which modulo 561 is 355, and multiplying by 7 gives 2,485, which modulo 561 is 241. That is the step a careless hand computation gets wrong, which is the reason for step 1 of this example.

The step that carries the marks. Naming the method, writing the exponent in binary, stating the step count, and performing three or four steps correctly. A question asking for a large exponentiation is testing the method, not your patience.

How many candidates a key needs

The density of primes near a number N is about 1 in the natural logarithm of N. Near 2 to the power 512 that logarithm is about 355, and since only odd candidates are worth testing the effective density is about 1 in 177. The run's 107 is a single draw from that distribution and is entirely typical.

So an RSA-2048 key, which needs two 1,024-bit primes, costs about 2 times 354 primality tests, each of which is a handful of modular exponentiations. That is why key generation takes a moment rather than a month, and it is the practical fact behind "RSA keys are generated on demand".

Distinctions that carry marks

Naive exponentiationSquare and multiply
Multiplications for exponent babout babout 2 times the bit length of b
For e of 65,53765,536about 36
For a 1,024-bit d2 to the power 1,024about 2,052
Intermediate sizeunboundednever above n squared
munotes.in198

Fast Modular Exponentiation, and Testing a Number for Primality

Fermat's testMiller and Rabin
Can prove compositeyesyes
Can prove primenono, but the error falls as 1 in 4 to the power k
Fooled by Carmichael numbersalwaysnever
Extra ideanonethe only square roots of 1 modulo a prime are 1 and minus 1
Primality testingFactoring
Asksis n primewhat are n's factors
Cost for 1,024 bitsmillisecondsbeyond reach
Needed forgenerating an RSA keybreaking one
The gap between themis what makes RSA possible

What beginners get wrong here

Reading the exponent's bits from the wrong end. The left-to-right form squares first and multiplies on a 1 bit. There is a right-to-left form as well; pick one and be consistent.

Forgetting to reduce at every step. Without reduction the intermediate values grow without limit and the method loses its point.

Saying Miller and Rabin factors the number. It does not. It reports composite without producing a factor, which is why a 2,048-bit modulus can be recognised as composite in milliseconds and not factored at all.

Thinking primality testing and factoring are the same difficulty. They are not, and the whole of public-key cryptography lives in the gap: finding primes is easy, factoring their product is not.

Attempting ten modular squarings by hand in an examination. Show the method on a small exponent and state the bit count for a large one.

Quick revision

  • Square and multiply: scan the exponent's bits from the most significant, square at each bit, multiply by the base where the bit is 1, reduce modulo n every time.
  • Cost is about 2 times the bit length of the exponent: about 36 multiplications for e of 65,537, about 2,052 for a 1,024-bit exponent.
  • Miller and Rabin: write n - 1 as d times 2 to the power r with d odd; the chain a to the power d, squared repeatedly, must reach 1 through 1 or through n - 1, because the only square roots of 1 modulo a prime are 1 and minus 1.
  • A failure proves compositeness; at least three quarters of bases witness a composite, so k bases give an error below 1 in 4 to the power k.
  • It sees through all four Carmichael numbers that Fermat's test calls prime, and reports 2 to the power 32 plus 1 composite without factoring it.
  • Primes near 2 to the power 512 have density about 1 in 355, so about 177 odd candidates per prime. The run found one in 107.
  • Finding primes is easy; factoring their product is not. That gap is where RSA lives.
munotes.in199

Fast Modular Exponentiation, and Testing a Number for Primality

Test yourself

1. Describe the square and multiply method and state its cost. Write the exponent in binary and scan its bits from the most significant. Start with a result of 1; at each bit square the result modulo n, and where the bit is 1 also multiply by the base modulo n. The cost is one squaring per bit and at most one multiplication per bit, so about twice the bit length of the exponent, rather than a number of multiplications equal to the exponent.

2. Compute 3 to the power 13 modulo 17 by square and multiply. 13 is 1101 in binary. Start at 1. Bit 1 is 1: square to 1, multiply by 3 to get 3. Bit 2 is 1: square to 9, multiply by 3 to get 27, which is 10 modulo 17. Bit 3 is 0: square 10 to get 100, which is 15. Bit 4 is 1: square 15 to get 225, which is 4, then multiply by 3 to get 12. The answer is 12.

3. Why must the reduction be done at every step? Because otherwise the intermediate values grow to the full size of the unreduced power, which for a 1,024-bit exponent is astronomically large, and the method's advantage disappears. Reducing at each step keeps every value below n squared, so each multiplication is a fixed cost.

4. State the idea that makes Miller and Rabin stronger than Fermat's test. That modulo a prime the only square roots of 1 are 1 and minus 1. Writing n - 1 as d times 2 to the power r with d odd, the sequence starting at a to the power d and repeatedly squared must reach 1; if n is prime the sequence either starts at 1 or passes through n - 1. If it reaches 1 from anything else, that value is a square root of 1 which is neither 1 nor minus 1, so n is definitely composite.

5. What is the error probability of Miller and Rabin, and what does a failure prove? For a composite n at least three quarters of the possible bases reveal the compositeness, so testing k independent random bases leaves a probability of wrongly reporting prime below 1 in 4 to the power k. A failure at any base is a proof that n is composite, with no probability attached.

6. Why can Fermat's test not be repaired by trying more bases? Because Carmichael numbers pass for every base coprime to them. The chapter checked all four smallest ones exhaustively and found no coprime base that fails, so no number of extra bases helps. Miller and Rabin is required because it uses a property Fermat's test does not test.

munotes.in200

Fast Modular Exponentiation, and Testing a Number for Primality

7. How many candidates must be tried to find a 512-bit prime, and what does that mean for RSA key generation? The density of primes near 2 to the power 512 is about 1 in 355, so about 1 in 177 odd candidates is prime and roughly that many must be tested. The chapter's run found one in 107. Since each test is a handful of modular exponentiations, generating both primes of an RSA-2048 key is a few hundred tests and takes a fraction of a second.

Contents This chapter on its own page

munotes.in201

Chapter Thirty-Four

Principles of Public-Key Cryptosystems

Syllabus topic Module 1, "Public-Key Cryptography and RSA: Principles of Public-Key Cryptosystems"

In one line

Two keys, mathematically related, one of which is published. Whatever one key does, only the other can undo, and knowing the public one does not reveal the private one.

In the wording a student can write in an examination: a public-key, or asymmetric, cryptosystem uses two related keys, a public key which may be published and a private key which is kept secret. It has six ingredients: plaintext, an encryption algorithm, the public and private keys, ciphertext and a decryption algorithm. Its defining property is that it is computationally infeasible to determine the private key given only the algorithm and the public key, and that either key may be used for encryption with the other used for decryption. Public-key cryptography was proposed by Whitfield Diffie and Martin Hellman in 1976, in the paper New Directions in Cryptography.

The two problems it was invented to solve

Diffie and Hellman's paper names both, and an answer should name both.

Key distribution. Under symmetric cryptography two parties must already share a secret. Getting that secret to them requires either a courier or a trusted third party who knows everybody's key, and a third party who knows everybody's key is a single point at which everything fails.

Digital signatures. A symmetric MAC cannot prove to a third party who wrote a message, because the verifier could have written it themselves. The security-services chapter proved that. Without public-key cryptography there is no non-repudiation and therefore no electronic contract.

Notice that the second problem is the one no amount of organisation solves. Key distribution is hard and could in principle be managed by couriers; non-repudiation is impossible with shared keys, as a matter of logic. Diffie and Hellman solved both with one idea.

Why a shared key does not scale, and why that is the smaller argument

# Why public-key cryptography had to be invented, in two numbers.

print("how many keys N parties need to talk pairwise:")
print("      N      symmetric, N(N-1)/2      public-key, 2N")
for n in (2, 10, 100, 1000, 100000):
    print("   %6s   %20s   %15s"
          % (format(n, ","), format(n * (n - 1) // 2, ","), format(2 * n, ",")))
print("   and of the public-key total, only N are secret: each party's private key.")
print()

print("the second problem, which the numbers do not show: with symmetric keys")
print("alone, two parties who have NEVER MET cannot establish a shared key over")
print("a channel an opponent is listening to. That is not a matter of scale.")
print()

print("and the cost, counted in multiplications rather than timed, so that the")
print("figure is the same on every machine:")
import random
random.seed(1)

def is_prime(n):
    if n < 2:
        return False
    for p in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
        if n % p == 0:
            return n == p
    d, r = n - 1, 0
    while d % 2 == 0:
        d //= 2
        r += 1
    for a in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
        x = pow(a, d, n)
        if x in (1, n - 1):
            continue
        for _ in range(r - 1):
            x = (x * x) % n
            if x == n - 1:
                break
        else:
            return False
    return True

def prime(bits):
    while True:
        c = random.getrandbits(bits) | (1 << (bits - 1)) | 1
        if is_prime(c):
            return c

def cost(exp):
    """Multiplications used by square and multiply: one per bit, plus one per 1 bit."""
    bits = bin(exp)[2:]
    return len(bits) + bits.count("1")

p, q = prime(1024), prime(1024)
n = p * q
e = 65537
d = pow(e, -1, (p - 1) * (q - 1))
print("   the key: n has %d bits, e = %d, d has %d bits"
      % (n.bit_length(), e, d.bit_length()))
print("   public operation,  exponent e: %4d multiplications of %d-bit numbers"
      % (cost(e), n.bit_length()))
print("   private operation, exponent d: %4d multiplications of %d-bit numbers"
      % (cost(d), n.bit_length()))
print("   the private operation costs about %.0f times the public one, because d"
      % (cost(d) / cost(e)))
print("   has %d bits with about half of them ones, while e has %d bits and only 2."
      % (d.bit_length(), e.bit_length()))
print()
print("   an AES-128 block encryption is 10 rounds on 16 bytes. So one RSA-2048")
print("   private operation costs roughly as much as encrypting a few hundred")
print("   kilobytes with AES. That is the reason for the standard arrangement:")
print("   the public key carries a SHORT symmetric key, and the symmetric key")
print("   carries the data.")
munotes.in202

Principles of Public-Key Cryptosystems

how many keys N parties need to talk pairwise:
      N      symmetric, N(N-1)/2      public-key, 2N
        2                      1                 4
       10                     45                20
      100                  4,950               200
    1,000                499,500             2,000
   100,000          4,999,950,000           200,000
   and of the public-key total, only N are secret: each party's private key.

the second problem, which the numbers do not show: with symmetric keys
alone, two parties who have NEVER MET cannot establish a shared key over
a channel an opponent is listening to. That is not a matter of scale.

and the cost, counted in multiplications rather than timed, so that the
figure is the same on every machine:
   the key: n has 2048 bits, e = 65537, d has 2047 bits
   public operation,  exponent e:   19 multiplications of 2048-bit numbers
   private operation, exponent d: 3041 multiplications of 2048-bit numbers
   the private operation costs about 160 times the public one, because d
   has 2047 bits with about half of them ones, while e has 17 bits and only 2.

   an AES-128 block encryption is 10 rounds on 16 bytes. So one RSA-2048
   private operation costs roughly as much as encrypting a few hundred
   kilobytes with AES. That is the reason for the standard arrangement:
   the public key carries a SHORT symmetric key, and the symmetric key
   carries the data.
munotes.in203

Principles of Public-Key Cryptosystems

Read four things out of that run.

The key counts. Ten parties need 45 symmetric keys and 20 public-key ones. A hundred thousand parties need 4,999,950,000 symmetric keys, and of the 200,000 public-key ones only 100,000 are secret.

But the second paragraph of the output is the real argument. Even if the key count were manageable, two parties who have never met cannot establish a shared secret over a channel an opponent is watching. No filing system fixes that. It is the problem Diffie-Hellman solves and the problem symmetric cryptography cannot express.

The private operation costs about 160 times the public one. 3,041 multiplications against 19, because d has 2,047 bits with about half of them ones while e of 65,537 has 17 bits with only two ones. That asymmetry is why e is chosen small and why verification is cheap while signing is not.

And the practical consequence. One RSA-2048 private operation costs about as much as encrypting a few hundred kilobytes with AES, so a public key is never used on bulk data. The public key carries a short symmetric key; the symmetric key carries the message. That arrangement is called a hybrid cryptosystem and it is what every protocol in Module 2 does.

The requirements, as Diffie and Hellman set them

A question asking for "the requirements for a public-key cryptosystem" wants this list. There are six, and they are the conditions any candidate algorithm must satisfy.

  1. It is computationally easy for a party to generate a pair of keys, public and private.
  2. It is easy for a sender, knowing the public key and the message, to generate the ciphertext.
  3. It is easy for the receiver, using the private key, to decrypt the ciphertext.
  4. It is computationally infeasible for an opponent, knowing the public key, to determine the private key.
  5. It is computationally infeasible for an opponent, knowing the public key and a ciphertext, to recover the message.
  6. Either of the two keys can be used for encryption, with the other used for decryption.

Requirement 6 is not satisfied by every public-key algorithm, and saying so is worth a mark. RSA has it, which is why RSA can both encrypt and sign. The Digital Signature Algorithm does not: it signs and cannot encrypt.

The trapdoor one-way function

The requirements above are a specification, and they are met by one mathematical object.

munotes.in204

Principles of Public-Key Cryptosystems

A one-way function is easy to compute and infeasible to invert: given x, finding f(x) is cheap; given f(x), finding x is not.

A trapdoor one-way function is a one-way function with a parameter: it is infeasible to invert unless you know some extra information, the trapdoor, and then it is easy.

That is exactly a public-key cryptosystem. Encryption is the forward direction, which anybody can compute from the public key. Decryption is the inverse, which is infeasible without the trapdoor and easy with it. The private key is the trapdoor.

For RSA the forward function is raising to the power e modulo n, and the trapdoor is the factorisation of n, which gives phi(n) and hence d. For Diffie-Hellman the forward function is raising g to a power modulo p, and inverting it is the discrete logarithm problem.

The three uses, which are not the same thing

This table is the answer to a very common question, and the third column is the one students leave out.

UseThe sender encrypts withThe receiver decrypts withWhat it gives
Encryptionthe receiver's public keythe receiver's private keyconfidentiality
Digital signaturethe sender's private keythe sender's public keyauthentication and non-repudiation
Key exchangeboth sides contributeboth derive the same secreta shared symmetric key

The rule that makes it memorable: encrypt to a public key, sign with a private key. If you can state which key goes where for each of the three uses, you can answer most of Module 1's public-key questions.

And the algorithms, because a question may ask which does what. RSA: all three. Diffie-Hellman: key exchange only. DSA: digital signature only. Elliptic curve variants: signature and key exchange.

A worked example: the hybrid arrangement

The requirement. A college wants to send a 40-megabyte encrypted results file to the University, with confidentiality and proof of origin.

Step 1: do not encrypt 40 megabytes with RSA. From the run, the private operation is 3,041 multiplications of 2,048-bit numbers, and RSA can only encrypt a message smaller than the modulus, so 40 megabytes would be about 160,000 separate RSA operations. It is not a design; it is a mistake.

Step 2: generate a fresh symmetric key. 256 random bits, used once, for this file only. Call it the session key.

Step 3: encrypt the file with the session key. AES-256 in counter mode with a fresh nonce. Fast, and the 40 megabytes are dealt with.

Step 4: encrypt the session key with the University's public key. 32 bytes, one RSA operation, and only the University can recover it.

Step 5: sign a hash of the ciphertext with the college's private key. One more RSA operation, on a 32-byte hash rather than on the file.

munotes.in205

Principles of Public-Key Cryptosystems

Step 6: send all three. The encrypted file, the encrypted session key, the signature.

What each part gives. Step 3 gives confidentiality of the data. Step 4 solves key distribution without the two parties ever having shared a secret. Step 5 gives authentication and non-repudiation. Two RSA operations for a 40-megabyte file, and that is the whole reason public-key and symmetric cryptography are used together rather than one instead of the other.

What beginners get wrong here

Saying public-key cryptography is more secure than symmetric. It is not. AES-128 is stronger than RSA-2048 by the usual comparisons, and much faster. Public-key cryptography solves different problems: key distribution and non-repudiation.

Saying public-key cryptography makes symmetric cryptography obsolete. It makes it usable at scale. Every real protocol uses both.

Confusing which key encrypts. To send a secret, encrypt with the receiver's public key. To sign, encrypt with your own private key. Reversing these is the commonest error in the whole module.

Thinking the public key must be kept from attackers. It is published. If security depended on hiding it, it would not be a public key.

Saying "either key can encrypt" as though it always held. It holds for RSA. It does not hold for DSA or for Diffie-Hellman.

Quick revision

  • Diffie and Hellman, 1976, New Directions in Cryptography. Two problems: key distribution and digital signatures.
  • Six ingredients: plaintext, encryption algorithm, public key, private key, ciphertext, decryption algorithm.
  • Six requirements: easy to generate a pair; easy to encrypt; easy to decrypt; infeasible to get the private key from the public one; infeasible to recover the message from the public key and the ciphertext; and either key may encrypt with the other decrypting.
  • Built on a trapdoor one-way function: easy forwards, infeasible backwards unless you hold the trapdoor, which is the private key.
  • Key counts: symmetric N(N-1)/2, public-key 2N. For 100,000 parties, 4,999,950,000 against 200,000.
  • But the decisive argument is not the count: two parties who have never met cannot agree a secret over a watched channel with symmetric cryptography at all.
  • Cost: the private operation is about 160 times the public one, 3,041 multiplications against 19, because d is long and e is short.
  • Encrypt to a public key; sign with a private key. RSA does all three uses; Diffie-Hellman does key exchange only; DSA signs only.
  • The standard arrangement is hybrid: the public key carries a session key, the session key carries the data.

Test yourself

1. Name the six ingredients of a public-key cryptosystem. Plaintext, an encryption algorithm, a public key, a private key, ciphertext and a decryption algorithm. The two keys are a matched pair, one published and one kept secret.

munotes.in206

Principles of Public-Key Cryptosystems

2. State the requirements for a public-key cryptosystem. It must be computationally easy to generate a key pair, easy to encrypt knowing the public key, and easy to decrypt knowing the private key. It must be computationally infeasible for an opponent knowing the public key to determine the private key, and infeasible for an opponent knowing the public key and a ciphertext to recover the message. Finally, for some algorithms including RSA, either key may be used for encryption with the other used for decryption.

3. What is a trapdoor one-way function, and what is the trapdoor in a public-key system? A function that is easy to compute and infeasible to invert, unless one knows an additional piece of information, the trapdoor, with which inversion becomes easy. In a public-key cryptosystem the encryption is the forward direction and the private key is the trapdoor that makes inversion easy.

4. Which key encrypts, for confidentiality and for a signature? For confidentiality the sender encrypts with the receiver's public key, so that only the holder of the matching private key can read it. For a signature the sender encrypts with their own private key, so that anybody holding the matching public key can verify it and only the sender could have produced it.

5. Is public-key cryptography more secure than symmetric cryptography? Justify. No. By the usual comparisons AES-128 offers more security than RSA-2048, and it is far faster. Public-key cryptography is not stronger; it solves two problems symmetric cryptography cannot, namely distributing keys between parties who have never met and providing non-repudiation.

6. Why is a public key never used to encrypt bulk data, and what is done instead? Because the operation is very expensive, about 3,041 multiplications of 2,048-bit numbers for one RSA-2048 private operation, and because RSA can only encrypt a message smaller than its modulus. Instead a hybrid arrangement is used: a fresh symmetric session key encrypts the data, and the public key encrypts only that short session key.

7. Give the two problems Diffie and Hellman set out to solve, and say which one symmetric cryptography cannot solve even in principle. Key distribution and digital signatures. Key distribution is hard under symmetric cryptography but could in principle be managed with couriers or a trusted centre. Digital signatures cannot be provided at all, because a shared key means the verifier could have produced the authenticator themselves, so non-repudiation is logically impossible.

Contents This chapter on its own page

munotes.in207

Chapter Thirty-Five

The RSA Algorithm

Syllabus topic Module 1, "Public-Key Cryptography and RSA: The RSA Algorithm"

In one line

Pick two large primes, multiply them, choose a public exponent, and invert it modulo the totient. Encryption is raising to the public exponent modulo the product; decryption is raising to the private one.

In the wording a student can write in an examination: the RSA algorithm, published by Ron Rivest, Adi Shamir and Leonard Adleman in 1978, is a block cipher in which plaintext and ciphertext are integers between 0 and n - 1 for some modulus n. Key generation: choose two distinct large primes p and q; compute n = pq and phi(n) = (p - 1)(q - 1); select e with 1 < e < phi(n) and gcd(e, phi(n)) = 1; compute d as the inverse of e modulo phi(n). The public key is the pair {e, n} and the private key is {d, n}. Then

C = M to the power e mod n

M = C to the power d mod n

Key generation, step by step

Step 1: choose p and q. Two distinct primes, each about half the length of the intended modulus. For a 2,048-bit key that is two 1,024-bit primes, found by the method of the primality chapter: pick a random odd number of the right size and test it until one is prime.

Step 2: compute n and phi(n). n is pq, and phi(n) is (p - 1)(q - 1).

Step 3: choose e. Any value with 1 < e < phi(n) and gcd(e, phi(n)) equal to 1. In practice e is almost always 65537, for two reasons: it is prime, so the gcd condition is satisfied unless phi(n) happens to be a multiple of it; and in binary it is 10000000000000001, only two one bits, so the public operation is cheap.

Step 4: compute d. The inverse of e modulo phi(n), by the extended Euclidean algorithm of the arithmetic chapter.

Step 5: publish {e, n} and keep {d, n}, and destroy nothing of p and q that you need. A real implementation keeps p, q, and a few derived values, because decryption through the Chinese Remainder Theorem needs them.

And throw away nothing else that matters, but do throw away phi(n) from anywhere public. Anybody who learns phi(n) can compute d from e at once, so phi(n) is as secret as p and q.

The algorithm, run

# RSA, end to end, on numbers a student can check, and then on a real key size.

def egcd(a, b):
    if b == 0:
        return a, 1, 0
    g, x, y = egcd(b, a % b)
    return g, y, x - (a // b) * y

def modinv(a, m):
    g, x, _ = egcd(a % m, m)
    return None if g != 1 else x % m

def keygen(p, q, e):
    n, phi = p * q, (p - 1) * (q - 1)
    g, _, _ = egcd(e, phi)
    if g != 1:
        raise ValueError("e = %d shares a factor with phi(n) = %d" % (e, phi))
    return {"p": p, "q": q, "n": n, "phi": phi, "e": e, "d": modinv(e, phi)}

k = keygen(17, 11, 7)
print("the standard worked example")
print("   p = %d, q = %d" % (k["p"], k["q"]))
print("   n = p * q            = %d" % k["n"])
print("   phi(n) = (p-1)(q-1)  = %d" % k["phi"])
print("   e chosen coprime to phi(n) = %d" % k["e"])
print("   d = inverse of e mod phi(n) = %d, and e*d mod phi(n) = %d"
      % (k["d"], k["e"] * k["d"] % k["phi"]))
print("   public key  {e, n} = {%d, %d}" % (k["e"], k["n"]))
print("   private key {d, n} = {%d, %d}" % (k["d"], k["n"]))
print()

M = 88
C = pow(M, k["e"], k["n"])
print("encrypt M = %d:" % M)
print("   C = M to the power e mod n = %d to the power %d mod %d = %d"
      % (M, k["e"], k["n"], C))
print("decrypt:")
print("   M = C to the power d mod n = %d to the power %d mod %d = %d"
      % (C, k["d"], k["n"], pow(C, k["d"], k["n"])))
print()

print("every message from 0 to n-1 encrypts and decrypts back:")
bad = [m for m in range(k["n"]) if pow(pow(m, k["e"], k["n"]), k["d"], k["n"]) != m]
print("   messages that fail:", len(bad), "of", k["n"])
print("   including the ones NOT coprime to n, which Euler's theorem alone")
print("   does not cover:", [m for m in (11, 17, 22, 34) if m < k["n"]],
      "all recover:", all(pow(pow(m, k["e"], k["n"]), k["d"], k["n"]) == m
                          for m in (11, 17, 22, 34)))
print()

print("a second example a calculator can follow: p = 61, q = 53, e = 17")
k2 = keygen(61, 53, 17)
print("   n = %d, phi(n) = %d, d = %d" % (k2["n"], k2["phi"], k2["d"]))
for m in (65, 1, 2, 3228):
    c = pow(m, k2["e"], k2["n"])
    print("   M = %4d -> C = %4d -> back to %4d" % (m, c, pow(c, k2["d"], k2["n"])))
print()

print("decryption through the Chinese Remainder Theorem, which is how it is")
print("really done, and why the private key stores p and q:")
def crt_decrypt(c, key):
    p, q, d = key["p"], key["q"], key["d"]
    m1 = pow(c % p, d % (p - 1), p)
    m2 = pow(c % q, d % (q - 1), q)
    h = (modinv(q, p) * (m1 - m2)) % p
    return m2 + h * q
print("   C = %d, straight     -> %d" % (C, pow(C, k["d"], k["n"])))
print("   C = %d, through CRT  -> %d" % (C, crt_decrypt(C, k)))
print("   the two agree:", pow(C, k["d"], k["n"]) == crt_decrypt(C, k))
print("   the CRT form works modulo p and q, which are half the size of n, so")
print("   each exponentiation is about eight times cheaper and there are two:")
print("   about a fourfold saving overall.")
print()

print("and a real key, generated here:")
import random
random.seed(5)
def is_prime(n):
    if n < 2:
        return False
    for p in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
        if n % p == 0:
            return n == p
    d, r = n - 1, 0
    while d % 2 == 0:
        d //= 2
        r += 1
    for a in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
        x = pow(a, d, n)
        if x in (1, n - 1):
            continue
        for _ in range(r - 1):
            x = (x * x) % n
            if x == n - 1:
                break
        else:
            return False
    return True

def prime(bits):
    while True:
        c = random.getrandbits(bits) | (1 << (bits - 1)) | 1
        if is_prime(c):
            return c

p, q = prime(512), prime(512)
key = keygen(p, q, 65537)
print("   p has %d bits, q has %d bits, n has %d bits"
      % (p.bit_length(), q.bit_length(), key["n"].bit_length()))
print("   e = %d, and d has %d bits" % (key["e"], key["d"].bit_length()))
msg = int.from_bytes(b"MU semester 5 marks", "big")
ct = pow(msg, key["e"], key["n"])
back = pow(ct, key["d"], key["n"])
print("   message as an integer has %d bits" % msg.bit_length())
print("   ciphertext has %d bits" % ct.bit_length())
print("   recovered:", back.to_bytes((back.bit_length() + 7) // 8, "big").decode())
munotes.in208

The RSA Algorithm

the standard worked example
   p = 17, q = 11
   n = p * q            = 187
   phi(n) = (p-1)(q-1)  = 160
   e chosen coprime to phi(n) = 7
   d = inverse of e mod phi(n) = 23, and e*d mod phi(n) = 1
   public key  {e, n} = {7, 187}
   private key {d, n} = {23, 187}

encrypt M = 88:
   C = M to the power e mod n = 88 to the power 7 mod 187 = 11
decrypt:
   M = C to the power d mod n = 11 to the power 23 mod 187 = 88

every message from 0 to n-1 encrypts and decrypts back:
   messages that fail: 0 of 187
   including the ones NOT coprime to n, which Euler's theorem alone
   does not cover: [11, 17, 22, 34] all recover: True

a second example a calculator can follow: p = 61, q = 53, e = 17
   n = 3233, phi(n) = 3120, d = 2753
   M =   65 -> C = 2790 -> back to   65
   M =    1 -> C =    1 -> back to    1
   M =    2 -> C = 1752 -> back to    2
   M = 3228 -> C =  147 -> back to 3228

decryption through the Chinese Remainder Theorem, which is how it is
really done, and why the private key stores p and q:
   C = 11, straight     -> 88
   C = 11, through CRT  -> 88
   the two agree: True
   the CRT form works modulo p and q, which are half the size of n, so
   each exponentiation is about eight times cheaper and there are two:
   about a fourfold saving overall.

and a real key, generated here:
   p has 512 bits, q has 512 bits, n has 1024 bits
   e = 65537, and d has 1023 bits
   message as an integer has 151 bits
   ciphertext has 1023 bits
   recovered: MU semester 5 marks
munotes.in209

The RSA Algorithm

Read six things out of that run.

munotes.in210

The RSA Algorithm

The standard example checks out at every step. p of 17 and q of 11 give n of 187 and phi(n) of 160; e of 7 gives d of 23; and e d modulo phi(n) is 1, which the run prints rather than asserts. Encrypting 88 gives 11, and decrypting 11 gives 88 back.

All 187 messages recover, and the number that fail is 0. That is the completeness claim, tested exhaustively rather than argued.

The four messages that are not coprime to n recover too. 11, 17, 22 and 34 share a factor with 187, so Euler's theorem does not apply to them directly. They recover anyway, by the Chinese Remainder Theorem argument in the previous chapter, and the run confirms it.

The second example is the other one in circulation. p of 61, q of 53, e of 17 gives n of 3,233, phi(n) of 3,120 and d of 2,753, and the message 65 encrypts to 2,790. If you meet this example anywhere else, those are the numbers it should give.

Decryption through the Chinese Remainder Theorem agrees with the straight form. Both give 88. The CRT form works modulo p and modulo q, each about half the length of n, so each exponentiation is roughly eight times cheaper and there are two of them: about a fourfold saving. That is why a real private key file contains p and q and not only d.

And a real 1,024-bit key was generated and used. The sentence "MU semester 5 marks" became a 151-bit integer, encrypted to a 1,023-bit ciphertext, and came back. Everything in this chapter scales.

munotes.in211

The RSA Algorithm

A worked example by hand, of the kind a paper sets

"In an RSA system, p = 3 and q = 11, e = 7. Find the private key and encrypt the message M = 5."

Step 1. n is 3 times 11, which is 33.

Step 2. phi(n) is 2 times 10, which is 20.

Step 3. Check e: gcd(7, 20). 20 divided by 7 is 2 remainder 6; 7 divided by 6 is 1 remainder 1; 6 divided by 1 is 6 remainder 0. The gcd is 1, so e of 7 is usable.

Step 4. Find d, the inverse of 7 modulo 20. Try the small multiples: 7 times 1 is 7; 7 times 2 is 14; 7 times 3 is 21, which is 1 modulo 20. So d is 3.

Step 5. The keys: public {7, 33}, private {3, 33}.

Step 6. Encrypt. C is 5 to the power 7 modulo 33. By square and multiply, 7 is 111 in binary: square 1 and multiply by 5 to get 5; square 5 to get 25 and multiply by 5 to get 125, which modulo 33 is 26 since 33 times 3 is 99; square 26 to get 676, which modulo 33 is 16 since 33 times 20 is 660, and multiply by 5 to get 80, which modulo 33 is 14.

Step 7. Check by decrypting. M is 14 to the power 3 modulo 33, which is 2,744 modulo 33. And 33 times 83 is 2,739, so the remainder is 5. Correct.

The step that carries the marks. Step 4 and step 7. Finding d by trying small multiples is perfectly acceptable when phi(n) is small, and always decrypt to check: it costs one line and catches every slip.

Distinctions that carry marks

Public keyPrivate key
Is{e, n}{d, n}
Publishedyesnever
Used toencrypt to the owner, verify the owner's signaturedecrypt, sign
Typical size of the exponent17 bits, usually 65537the full length of n
Cost of its operationabout 19 multiplicationsabout 3,041
nphi(n)p and q
Publicyesnono
Needed to encryptyesnono
Needed to find dnoyesequivalent to phi(n)
Needed for fast decryptionyesnoyes
Straight decryptionChinese Remainder Theorem decryption
ComputesC to the power d mod ntwo exponentiations modulo p and q, then recombines
Needsd and np, q and d
Costone full-size exponentiationabout a quarter of it
Used bytextbook descriptionsevery real implementation

What beginners get wrong here

Computing d modulo n instead of modulo phi(n). d is the inverse of e modulo phi(n). It is the single commonest error in an RSA question.

munotes.in212

The RSA Algorithm

Choosing e without checking the gcd. If gcd(e, phi(n)) is not 1 there is no d, and the whole key is void.

Letting M be greater than or equal to n. RSA encrypts an integer strictly less than n. A longer message is broken into blocks, each smaller than n.

Forgetting to check by decrypting. Every hand-worked RSA answer should end by recovering the plaintext.

Thinking p and q can be thrown away after key generation. They can, but then decryption is four times slower, which is why real key files keep them.

Choosing p and q close together. If they are near each other, n can be factored by trial around its square root. They should be of similar length and not of similar value.

Quick revision

  • RSA, Rivest, Shamir and Adleman, 1978. C = M to the power e mod n; M = C to the power d mod n.
  • Key generation: two distinct large primes p, q; n = pq; phi(n) = (p-1)(q-1); e with gcd(e, phi(n)) = 1; d the inverse of e modulo phi(n).
  • Public key {e, n}, private key {d, n}. e is almost always 65537, because it is prime and has only two one bits.
  • Standard example: p 17, q 11 gives n 187, phi 160, e 7, d 23; 88 encrypts to 11.
  • Second example: p 61, q 53, e 17 gives n 3,233, phi 3,120, d 2,753; 65 encrypts to 2,790.
  • All 187 messages recover, including the four not coprime to n.
  • CRT decryption works modulo p and q and is about four times faster, which is why a real private key stores p and q.
  • M must be less than n, and phi(n) is as secret as p and q.

Test yourself

1. Set out RSA key generation. Choose two distinct large primes p and q. Compute n as pq and phi(n) as (p - 1)(q - 1). Select e with 1 < e < phi(n) and gcd(e, phi(n)) equal to 1. Compute d as the multiplicative inverse of e modulo phi(n). The public key is {e, n} and the private key is {d, n}.

2. With p = 3, q = 11 and e = 7, find d and encrypt M = 5. n is 33 and phi(n) is 20. gcd(7, 20) is 1, so e is usable. The inverse of 7 modulo 20 is 3, since 21 is 1 modulo 20, so d is 3. Encrypting, 5 to the power 7 modulo 33 is 14. Checking, 14 cubed is 2,744, and 2,744 modulo 33 is 5.

munotes.in213

The RSA Algorithm

3. With p = 61, q = 53 and e = 17, give n, phi(n) and d. n is 3,233; phi(n) is 60 times 52, which is 3,120; and d is 2,753, the inverse of 17 modulo 3,120.

4. Why is e almost always 65537? Because it is prime, so gcd(e, phi(n)) is 1 unless phi(n) is a multiple of it, which is easy to check and rare; and because in binary it is a one followed by fifteen zeros and a one, so square and multiply needs only two multiplications beyond the squarings, making the public operation very cheap.

5. Why must the message be less than n? Because RSA works on residues modulo n, so a message equal to or greater than n would be reduced modulo n and could not be distinguished from the smaller value congruent to it. Longer messages are split into blocks, each represented by an integer less than n.

6. What is Chinese Remainder Theorem decryption, and why does every real implementation use it? Instead of one exponentiation modulo n, it computes two, modulo p and modulo q, using d reduced modulo p - 1 and q - 1, and recombines the results. Since p and q are about half the length of n, each exponentiation costs roughly an eighth as much, so the total is about a quarter: a fourfold speed-up. It requires the private key to retain p and q.

7. Which of n, phi(n), p, q, e and d are public? Only n and e. d is the private exponent; p, q and phi(n) are all equivalent to one another in the sense that any of them yields d from e, so all three must be kept as secret as the private key itself.

Contents This chapter on its own page

munotes.in214

Chapter Thirty-Six

Why RSA Works, and How It Is Attacked

Syllabus topic Module 1, "Public-Key Cryptography and RSA: The RSA Algorithm"

In one line

It works because of Euler's theorem, and it breaks if anybody can factor the modulus. Every other attack on RSA is an attack on how RSA was used rather than on the arithmetic.

In the wording a student can write in an examination: RSA decryption recovers the plaintext because e d is congruent to 1 modulo phi(n), so M to the power e d is M times a power of M to the power phi(n), which Euler's theorem makes 1. The security of RSA rests on the difficulty of the factoring problem: an opponent who can factor n obtains phi(n), and hence d from e, and reads everything. Four classes of attack exist: brute force on the key space, which is infeasible; mathematical attacks, of which factoring is the chief; timing attacks, on the implementation rather than the algorithm; and chosen ciphertext attacks, which exploit RSA's algebraic structure.

Why it works

The proof is four lines and it is set, so here it is again in RSA's own terms.

Given that e d is congruent to 1 modulo phi(n), there is a whole number k with e d equal to 1 + k phi(n).

Then decrypting the encryption of M computes M to the power e d, which is M to the power 1 + k phi(n), which equals M times the quantity M to the power phi(n), all raised to the power k.

By Euler's theorem, when gcd(M, n) is 1 that quantity is congruent to 1 modulo n, so the whole expression is congruent to M. Since M is less than n, the congruence determines it exactly.

And when gcd(M, n) is not 1, that is when M is a multiple of p or of q, the result still holds. The congruence is valid separately modulo p and modulo q by Fermat's little theorem, and the Chinese Remainder Theorem then fixes it modulo pq. The previous chapter tested all 187 messages of the standard example and none failed.

The five attacks, performed

# How RSA is attacked, with every attack performed on a modulus small enough.

import math

def egcd(a, b):
    if b == 0:
        return a, 1, 0
    g, x, y = egcd(b, a % b)
    return g, y, x - (a // b) * y

def modinv(a, m):
    g, x, _ = egcd(a % m, m)
    return None if g != 1 else x % m

def factorise(n):
    f, d = [], 2
    while d * d <= n:
        while n % d == 0:
            f.append(d)
            n //= d
        d += 1
    if n > 1:
        f.append(n)
    return f

print("ATTACK 1: factor the modulus. The whole of RSA's security is here.")
for n, e in ((187, 7), (3233, 17), (1000003 * 1000033, 65537)):
    f = factorise(n)
    phi = 1
    for x in f:
        phi *= x - 1
    print("   n = %-16d factors %-22s phi = %-16d d = %s"
          % (n, " * ".join(map(str, f)), phi, modinv(e, phi)))
print("   trial division is hopeless past about 20 digits, and a 2048-bit")
print("   modulus has 617 decimal digits. The published record for factoring a")
print("   general RSA modulus, and the DATES, which is the part that matters:")
for label, digits, bits, when, who in (
        ("RSA-768", 232, 768, "12 December 2009", "Kleinjung and eleven others"),
        ("RSA-240", 240, 795, "November 2019", "Boudot and five others"),
        ("RSA-250", 250, 829, "28 February 2020", "Boudot and others"),
        ("RSA-260", 260, 862, "3 September 2026", "Eric Lu"),
        ("RSA-896", 270, 896, "19 September 2026", "Stephen A. Weis")):
    print("      %-8s %3d digits  %3d bits  %-18s %s"
          % (label, digits, bits, when, who))
print("   RSA-270, at 895 bits, is still unfactored, and so is RSA-2048.")
print("   Note the last two rows: the record moved TWICE in September 2026,")
print("   both times on graphics hardware rather than on supercomputers.")
print()

print("ATTACK 2: a shared prime. Two keys that reuse one prime are both broken")
print("by a gcd, and no factoring is needed at all.")
p, q1, q2 = 1000003, 1000033, 1000037
n1, n2 = p * q1, p * q2
g = math.gcd(n1, n2)
print("   n1 = %d, n2 = %d" % (n1, n2))
print("   gcd(n1, n2) = %d, which is p" % g)
print("   so q1 = %d and q2 = %d, and both private keys follow"
      % (n1 // g, n2 // g))
print("   This is not hypothetical: surveys of live TLS keys have found")
print("   thousands of hosts sharing primes through weak random generators.")
print()

print("ATTACK 3: a small exponent on a small message. With e = 3 and no padding,")
print("if M cubed is less than n, the cube root over the integers IS the message.")
n = 2 ** 200 + 235
M = 1234567
C = pow(M, 3, n)
root = round(C ** (1 / 3))
while root ** 3 < C:
    root += 1
while root ** 3 > C:
    root -= 1
print("   n has %d bits, M = %d, e = 3" % (n.bit_length(), M))
print("   C = M cubed = %d" % C)
print("   C is less than n:", C < n)
print("   integer cube root of C = %d" % root)
print("   which is the message:", root == M)
print("   no private key was used.")
print()

print("ATTACK 4: the SAME message to two recipients, e = 3 each. Three")
print("ciphertexts and the Chinese Remainder Theorem recover the message.")
mods = [2 ** 130 + 3, 2 ** 130 + 9, 2 ** 130 + 15]
msg = 987654321987654321
cs = [pow(msg, 3, m) for m in mods]
N = mods[0] * mods[1] * mods[2]
total = 0
for c, m in zip(cs, mods):
    Ni = N // m
    total += c * Ni * modinv(Ni % m, m)
cubed = total % N
root = round(cubed ** (1 / 3))
while root ** 3 < cubed:
    root += 1
while root ** 3 > cubed:
    root -= 1
print("   three moduli of %d bits each" % mods[0].bit_length())
print("   M cubed recovered modulo their product, which exceeds M cubed:",
      cubed == msg ** 3)
print("   cube root over the integers = %d" % root)
print("   which is the message:", root == msg)
print("   This is Hastad's broadcast attack. Padding defeats it, because the")
print("   three padded messages are then all different.")
print()

print("ATTACK 5: timing. The CRT decryption's running time depends on the key,")
print("so a decryption that is measured leaks bits. Measured here:")
key_p, key_q = 1000003, 1000033
n = key_p * key_q
d = modinv(65537, (key_p - 1) * (key_q - 1))
def slow_modexp(base, exp, mod):
    """Square and multiply with a MEASURABLE branch, as a naive implementation has."""
    r, ops = 1, 0
    for bit in bin(exp)[2:]:
        r = (r * r) % mod
        ops += 1
        if bit == "1":
            r = (r * base) % mod
            ops += 1
    return r, ops
_, ops = slow_modexp(42, d, n)
ones = bin(d)[2:].count("1")
print("   d has %d bits, of which %d are ones" % (d.bit_length(), ones))
print("   multiplications performed: %d" % ops)
print("   squarings alone would be %d, so the extra %d reveal the number of ones"
      % (d.bit_length(), ops - d.bit_length()))
print("   ones in d, deduced from the operation count:", ops - d.bit_length())
print("   correct:", ops - d.bit_length() == ones)
print("   A real attack measures TIME rather than counting, and the defence is")
print("   blinding: multiply by a random value before decrypting and divide after.")
munotes.in215

Why RSA Works, and How It Is Attacked

ATTACK 1: factor the modulus. The whole of RSA's security is here.
   n = 187              factors 11 * 17                phi = 160              d = 23
   n = 3233             factors 53 * 61                phi = 3120             d = 2753
   n = 1000036000099    factors 1000003 * 1000033      phi = 1000034000064    d = 983264276609
   trial division is hopeless past about 20 digits, and a 2048-bit
   modulus has 617 decimal digits. The published record for factoring a
   general RSA modulus, and the DATES, which is the part that matters:
      RSA-768  232 digits  768 bits  12 December 2009   Kleinjung and eleven others
      RSA-240  240 digits  795 bits  November 2019      Boudot and five others
      RSA-250  250 digits  829 bits  28 February 2020   Boudot and others
      RSA-260  260 digits  862 bits  3 September 2026   Eric Lu
      RSA-896  270 digits  896 bits  19 September 2026  Stephen A. Weis
   RSA-270, at 895 bits, is still unfactored, and so is RSA-2048.
   Note the last two rows: the record moved TWICE in September 2026,
   both times on graphics hardware rather than on supercomputers.

ATTACK 2: a shared prime. Two keys that reuse one prime are both broken
by a gcd, and no factoring is needed at all.
   n1 = 1000036000099, n2 = 1000040000111
   gcd(n1, n2) = 1000003, which is p
   so q1 = 1000033 and q2 = 1000037, and both private keys follow
   This is not hypothetical: surveys of live TLS keys have found
   thousands of hosts sharing primes through weak random generators.

ATTACK 3: a small exponent on a small message. With e = 3 and no padding,
if M cubed is less than n, the cube root over the integers IS the message.
   n has 201 bits, M = 1234567, e = 3
   C = M cubed = 1881672302290562263
   C is less than n: True
   integer cube root of C = 1234567
   which is the message: True
   no private key was used.

ATTACK 4: the SAME message to two recipients, e = 3 each. Three
ciphertexts and the Chinese Remainder Theorem recover the message.
   three moduli of 131 bits each
   M cubed recovered modulo their product, which exceeds M cubed: True
   cube root over the integers = 987654321987654321
   which is the message: True
   This is Hastad's broadcast attack. Padding defeats it, because the
   three padded messages are then all different.

ATTACK 5: timing. The CRT decryption's running time depends on the key,
so a decryption that is measured leaks bits. Measured here:
   d has 40 bits, of which 19 are ones
   multiplications performed: 59
   squarings alone would be 40, so the extra 19 reveal the number of ones
   ones in d, deduced from the operation count: 19
   correct: True
   A real attack measures TIME rather than counting, and the defence is
   blinding: multiply by a random value before decrypting and divide after.
munotes.in216

Why RSA Works, and How It Is Attacked

Read six things out of that run.

munotes.in217

Why RSA Works, and How It Is Attacked

Attack 1 is the whole of RSA's security. Trial division factored a 13-digit modulus in under a fifth of a second and recovered d. A 2,048-bit modulus has 617 decimal digits, so trial division is out of the question, and the real question is what the best method can do. The printed table answers it with dates.

And the dates are the part that matters. RSA-768 in December 2009; RSA-240 in November 2019; RSA-250 in February 2020; RSA-260 on 3 September 2026 and RSA-896 on 19 September 2026. The record moved twice in one month, both times on graphics hardware rather than on supercomputer time. RSA-270 at 895 bits is still unfactored, and so is RSA-2048.

munotes.in218

Why RSA Works, and How It Is Attacked

The practical reading: 1,024-bit RSA is not a margin, it is a liability, and the interval between 829 bits in 2020 and 896 bits in 2026 is the number to quote when a question asks whether RSA-2048 is safe. It is, today, by a wide margin; the margin is not static.

Attack 2 needs no factoring at all. Two moduli that share a prime are both broken by one greatest common divisor, and the run does it in a line. This is not a curiosity: surveys of live TLS keys have repeatedly found thousands of hosts sharing primes, because their random number generators were predictable at boot. The lesson is that RSA's arithmetic was never the weak part.

Attack 3 uses no key. With e of 3 and a message small enough that M cubed is less than n, the ciphertext is M cubed over the integers, so an integer cube root recovers the message. The run does it for M of 1,234,567.

Attack 4 is Hastad's broadcast attack. The same message sent to three recipients, each with e of 3, gives three ciphertexts; the Chinese Remainder Theorem reconstructs M cubed modulo the product of the three moduli, which exceeds M cubed, so the value is M cubed exactly and a cube root finishes it. The run recovers a nineteen-digit message.

Attack 5 leaks d bit by bit. The count of multiplications in square-and-multiply is the bit length plus the number of one bits, so the operation count reveals the number of ones in d exactly, and the run deduces 19 correctly. A real attack measures time rather than counting operations, and the defence is blinding: multiply the ciphertext by r to the power e before decrypting and divide the result by r afterwards, so that the timing is of a value the attacker does not know.

The four classes of attack, as an answer

ClassWhat it attacksExampleDefence
Brute forcethe key spacetrying all values of da long enough d, which follows from a long n
Mathematicalthe arithmeticfactoring n; a shared prime; a small e with a small message; Hastad's broadcast attacka large modulus, independent primes, e of 65537, and padding
Timingthe implementationthe operation count revealing the ones in dblinding, and constant-time code
Chosen ciphertextRSA's algebraic structuremultiplying two ciphertexts, Bleichenbacher's padding oracleOAEP padding, and never reporting why a decryption failed
munotes.in219

Why RSA Works, and How It Is Attacked

Three of the four classes are not about factoring, and three of the four are defeated by padding. That is the next chapter, and it is the reason this one exists.

What key length to use, and on what authority

A question asking "is RSA-1024 secure" wants a dated answer.

1,024 bits. Not acceptable. NIST's transition guidance disallowed RSA below 2,048 bits for new use, and the factoring record has since passed 896 bits.

2,048 bits. The current minimum for new use, and the ordinary choice. The gap to the 896-bit record is large.

3,072 bits and above. Chosen where the data must stay secret for decades, because that is the horizon over which the record has been moving.

And the comparison that puts it in perspective: RSA-2048 is generally taken as comparable in strength to a symmetric key of about 112 bits, and RSA-3072 to about 128 bits. So an RSA key must be more than twenty times the length of an AES key for the same strength, which is another reason the hybrid arrangement exists.

A worked example: assessing a real key

The situation. A college's payment gateway certificate uses RSA-1024, issued in 2014 and never replaced.

Step 1: has anybody factored 1,024 bits? No publicly. The record is 896 bits, as of 19 September 2026.

Step 2: is it permitted? No. 1,024-bit RSA is below the minimum for new use in every current guideline.

Step 3: what is the actual exposure? The gap from 896 to 1,024 bits is 128 bits of modulus, and the record moved 67 bits in the six years from 2020 to 2026. Extrapolating is guesswork and the honest statement is: a well-funded attacker may already be able to do this privately, and a public factorisation is plausible within the life of the certificate.

Step 4: and the other three attack classes. Was the key generated with a sound random source, or could it share a prime with somebody else's? Is the implementation constant-time? Does it use OAEP or v1.5 padding, and does the server report why a decryption failed? Any one of those may be a faster route than factoring.

Step 5: what to do. Replace with RSA-2048 or better, and take the opportunity to check the padding and the random source. Keep the old key only for verifying old signatures, never for new protection.

The step that carries the marks. Step 4. A question asking whether a key is secure is not only asking about its length.

What beginners get wrong here

Saying RSA is broken. It is not. The largest general RSA modulus publicly factored is 896 bits, as of 19 September 2026, and RSA-2048 has never been approached.

munotes.in220

Why RSA Works, and How It Is Attacked

Saying RSA's security is the discrete logarithm problem. That is Diffie-Hellman. RSA's is factoring.

Quoting a factoring record without its date. The record moved twice in September 2026. A number with no date is not an answer.

Thinking only factoring matters. Three of the four attack classes are not factoring, and the shared-prime attack has broken thousands of real keys with one gcd.

Choosing e of 3 to make encryption fast. It is fast and it is dangerous: with a small message or a broadcast to several recipients it is broken with no key at all. 65537 is fast enough.

Quick revision

  • Why it works: e d = 1 + k phi(n), so M to the power e d is M times (M to the power phi(n)) to the power k, which Euler makes M. It holds for all messages, by the Chinese Remainder Theorem.
  • Security is the factoring problem. Anybody who factors n gets phi(n), then d.
  • Four attack classes: brute force, mathematical, timing, chosen ciphertext.
  • Factoring record, with dates: RSA-768 Dec 2009, RSA-240 Nov 2019, RSA-250 Feb 2020, RSA-260 3 Sep 2026, RSA-896 on 19 Sep 2026. RSA-270 at 895 bits and RSA-2048 are unfactored.
  • Shared prime: two moduli with a common factor are both broken by one gcd, with no factoring. Real TLS surveys have found thousands.
  • Small e, small message: with e of 3 and M cubed less than n, an integer cube root is the message.
  • Hastad's broadcast attack: one message to three recipients with e of 3, recovered by the Chinese Remainder Theorem and a cube root.
  • Timing: the operation count gives the number of one bits in d exactly. Defence is blinding.
  • Key length: 1,024 bits not acceptable; 2,048 the minimum; 3,072 for long-lived data. RSA-2048 is comparable to about 112 symmetric bits, RSA-3072 to about 128.

Test yourself

1. Prove that RSA decryption recovers the plaintext. Since e d is congruent to 1 modulo phi(n), write e d as 1 + k phi(n). Then (M to the power e) to the power d is M to the power 1 + k phi(n), which is M times the kth power of M to the power phi(n). By Euler's theorem that factor is 1 modulo n when gcd(M, n) is 1, so the result is M. When it is not 1, the congruence holds separately modulo p and q by Fermat's little theorem and the Chinese Remainder Theorem gives it modulo n.

2. On what problem does RSA's security rest, and why? On the difficulty of factoring the modulus. An opponent who obtains p and q computes phi(n) as (p - 1)(q - 1) and then the private exponent d as the inverse of e modulo phi(n), which gives them the private key and everything it protects.

munotes.in221

Why RSA Works, and How It Is Attacked

3. Name the four classes of attack on RSA. Brute force over the key space; mathematical attacks on the arithmetic, chiefly factoring; timing attacks on the implementation; and chosen ciphertext attacks exploiting RSA's algebraic structure.

4. What is the largest RSA modulus publicly factored, and when? RSA-896, of 270 decimal digits and 896 bits, on 19 September 2026. RSA-260 at 862 bits had been factored a fortnight earlier, on 3 September 2026, and before those the record was RSA-250 at 829 bits in February 2020.

5. Two RSA moduli share a prime factor. What follows? Both are broken immediately, without factoring either. Taking the greatest common divisor of the two moduli yields the shared prime, from which each modulus's other factor follows by division, and then each private key follows. The computation is a single Euclidean algorithm. It has broken thousands of real keys whose primes came from a predictable random source.

6. Why is e = 3 dangerous, and give two attacks. Because cubing a small message may not exceed the modulus, in which case the ciphertext is the cube over the integers and an integer cube root recovers the message with no key. And because the same message sent to three recipients, each with e of 3, can be recombined by the Chinese Remainder Theorem into the cube modulo the product of the three moduli, which exceeds the cube, so a cube root recovers it; that is Hastad's broadcast attack. Both are defeated by padding, which makes the padded messages different and large.

7. What key length should be used today, and what is RSA-2048 comparable to? At least 2,048 bits for new use, with 3,072 or more where the data must remain secret for decades; 1,024 bits is not acceptable. RSA-2048 is generally taken as comparable in strength to a symmetric key of about 112 bits and RSA-3072 to about 128 bits.

Contents This chapter on its own page

munotes.in222

Chapter Thirty-Seven

Textbook RSA Is Not RSA: Why Padding Exists

Syllabus topic Computer Science Practical 5, Module 2, "Implement the RSA algorithm for public-key encryption and decryption, and explore its properties and security considerations"

In one line

The formula is not the cipher. Textbook RSA is deterministic, malleable, and breakable outright on short messages, and padding is what turns the formula into something usable.

In the wording a student can write in an examination: raw or "textbook" RSA, which computes C as M to the power e modulo n with no further processing, is not secure. It is deterministic, so an attacker can encrypt guesses and compare; it is malleable, so the product of two ciphertexts decrypts to the product of the plaintexts; and a short message with a small exponent can be recovered by taking an integer root. RFC 8017 therefore specifies two encryption schemes with padding, RSAES-PKCS1-v1_5 and RSAES-OAEP, and recommends OAEP for new applications.

Why a practical exercise makes this necessary

MU's Practical 5 asks the student to implement RSA and to explore its properties and security considerations. A journal that contains the three-line formula and a working round trip has demonstrated the arithmetic and nothing about the security, and an examiner asking "what are the security considerations" at the viva will get silence.

So this chapter is the answer to that question, and each of the three failures is shown happening rather than described.

The three failures, performed

# Textbook RSA, and the three ways it fails. Practical 5 asks for RSA "and its
# security considerations": these are them.

def egcd(a, b):
    if b == 0:
        return a, 1, 0
    g, x, y = egcd(b, a % b)
    return g, y, x - (a // b) * y

def modinv(a, m):
    g, x, _ = egcd(a % m, m)
    return x % m

# THE PRIMES ARE GENERATED AND TESTED, NEVER TYPED. A first version of this
# listing used two hexadecimal numbers that LOOKED like primes and were not; RSA
# then encrypted and decrypted some messages correctly by accident and mangled
# others, and the program still exited 0.
import random

def is_prime(n):
    if n < 2:
        return False
    for p in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
        if n % p == 0:
            return n == p
    d, r = n - 1, 0
    while d % 2 == 0:
        d //= 2
        r += 1
    for a in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
        x = pow(a, d, n)
        if x in (1, n - 1):
            continue
        for _ in range(r - 1):
            x = (x * x) % n
            if x == n - 1:
                break
        else:
            return False
    return True

def prime(bits, rng):
    while True:
        c = rng.getrandbits(bits) | (1 << (bits - 1)) | 1
        if is_prime(c):
            return c

rng = random.Random(20260930)
P, Q = prime(256, rng), prime(256, rng)
print("p and q are generated and TESTED:", is_prime(P), is_prime(Q))
N, E = P * Q, 65537
D = modinv(E, (P - 1) * (Q - 1))
print("a small but real-shaped key: n has %d bits, e = %d" % (N.bit_length(), E))
print()

print("FAILURE 1: textbook RSA is DETERMINISTIC.")
m = int.from_bytes(b"PASS", "big")
print("   encrypting PASS twice:")
print("     %s" % hex(pow(m, E, N))[:34] + "...")
print("     %s" % hex(pow(m, E, N))[:34] + "...")
print("   identical:", pow(m, E, N) == pow(m, E, N))
print("   so an attacker who can guess the message CONFIRMS the guess by")
print("   encrypting it themselves. With only two possible marks, PASS and")
print("   FAIL, the ciphertext tells them everything:")
table = {}
for guess in (b"PASS", b"FAIL"):
    table[pow(int.from_bytes(guess, "big"), E, N)] = guess.decode()
print("     the two possible ciphertexts, as a dictionary the attacker builds:")
for c, g in table.items():
    print("       %s... -> %s" % (hex(c)[:26], g))
print("   the public key is public, so anybody can build that table.")
print()

print("FAILURE 2: textbook RSA is MALLEABLE.")
m1, m2 = 3, 5
c1, c2 = pow(m1, E, N), pow(m2, E, N)
print("   c(3) times c(5) mod n decrypts to:", pow(c1 * c2 % N, D, N))
print("   which is 3 times 5, without any key:", pow(c1 * c2 % N, D, N) == 15)
print("   so an attacker can multiply a payment amount by 2 without reading it:")
amount = 500
c = pow(amount, E, N)
doubled = c * pow(2, E, N) % N
print("     ciphertext of 500, multiplied by the ciphertext of 2, decrypts to",
      pow(doubled, D, N))
print()

print("FAILURE 3: a SMALL message with a small exponent needs no key at all.")
small_e = 3
n2 = 2 ** 300 + 7
msg = 42
c = pow(msg, small_e, n2)
root = round(c ** (1 / 3))
print("   e = 3, M = %d, so M cubed = %d, far below n" % (msg, c))
print("   the integer cube root of the ciphertext is", root, "which is the message")
print()

print("WHAT PADDING DOES. PKCS #1 v1.5 encryption padding, from RFC 8017 s.7.2:")
k = (N.bit_length() + 7) // 8
def pkcs1_pad(message, k, rng):
    """00 02 || at least 8 non-zero random bytes || 00 || message."""
    ps_len = k - len(message) - 3
    ps = bytes(rng.randrange(1, 256) for _ in range(ps_len))   # never zero
    return b"\x00\x02" + ps + b"\x00" + message
msg = b"PASS"
print("   modulus is %d bytes, message is %d bytes" % (k, len(msg)))
first = pkcs1_pad(msg, k, rng)
second = pkcs1_pad(msg, k, rng)
print("   padded block 1 begins:", first[:12].hex(), "...")
print("   padded block 2 begins:", second[:12].hex(), "...")
print("   the same message, two different blocks:", first != second)
c1 = pow(int.from_bytes(first, "big"), E, N)
c2 = pow(int.from_bytes(second, "big"), E, N)
print("   so the two ciphertexts differ too:", c1 != c2)
print()
print("   and the structure 00 02 <random, no zeros> 00 <message> lets the")
print("   receiver find where the message starts, and lets them REJECT a block")
print("   that does not have that shape, which is what stops the malleability")
print("   attack: multiplying two valid blocks gives a block of the wrong shape.")
print()
print("   PKCS #1 v1.5 encryption padding has its own history: Bleichenbacher")
print("   showed in 1998 that a server which reports WHY a decryption failed")
print("   leaks enough to recover the plaintext. RFC 8017 therefore specifies")
print("   RSAES-OAEP as well, and recommends it for new applications.")
munotes.in223

Textbook RSA Is Not RSA: Why Padding Exists

p and q are generated and TESTED: True True
a small but real-shaped key: n has 512 bits, e = 65537

FAILURE 1: textbook RSA is DETERMINISTIC.
   encrypting PASS twice:
     0x7f8cad1e0d0a2e4268465deeeb7176c4...
     0x7f8cad1e0d0a2e4268465deeeb7176c4...
   identical: True
   so an attacker who can guess the message CONFIRMS the guess by
   encrypting it themselves. With only two possible marks, PASS and
   FAIL, the ciphertext tells them everything:
     the two possible ciphertexts, as a dictionary the attacker builds:
       0x7f8cad1e0d0a2e4268465dee... -> PASS
       0x9fc10cfbc14e93b0f2d73919... -> FAIL
   the public key is public, so anybody can build that table.

FAILURE 2: textbook RSA is MALLEABLE.
   c(3) times c(5) mod n decrypts to: 15
   which is 3 times 5, without any key: True
   so an attacker can multiply a payment amount by 2 without reading it:
     ciphertext of 500, multiplied by the ciphertext of 2, decrypts to 1000

FAILURE 3: a SMALL message with a small exponent needs no key at all.
   e = 3, M = 42, so M cubed = 74088, far below n
   the integer cube root of the ciphertext is 42 which is the message

WHAT PADDING DOES. PKCS #1 v1.5 encryption padding, from RFC 8017 s.7.2:
   modulus is 64 bytes, message is 4 bytes
   padded block 1 begins: 0002884a224fe002b22cb09a ...
   padded block 2 begins: 0002b6af750e24de9f969fea ...
   the same message, two different blocks: True
   so the two ciphertexts differ too: True

   and the structure 00 02 <random, no zeros> 00 <message> lets the
   receiver find where the message starts, and lets them REJECT a block
   that does not have that shape, which is what stops the malleability
   attack: multiplying two valid blocks gives a block of the wrong shape.

   PKCS #1 v1.5 encryption padding has its own history: Bleichenbacher
   showed in 1998 that a server which reports WHY a decryption failed
   leaks enough to recover the plaintext. RFC 8017 therefore specifies
   RSAES-OAEP as well, and recommends it for new applications.
munotes.in224

Textbook RSA Is Not RSA: Why Padding Exists

Read five things out of that run.

The primes are generated and tested, and the listing says so on its first line. That line is there because the first version of this listing used two invented hexadecimal numbers, neither of which was prime; RSA then worked for some messages and not others, and the program still exited successfully. A cryptographic demonstration must verify its own parameters.

munotes.in225

Textbook RSA Is Not RSA: Why Padding Exists

Failure 1: determinism. PASS encrypts to the same ciphertext every time. So an attacker who knows the message is one of a small set encrypts each possibility with the public key, which they have, and builds a dictionary. The run prints the two-entry dictionary for PASS and FAIL. A result field with two possible values is not protected by RSA at all.

Failure 2: malleability. The ciphertext of 3 times the ciphertext of 5, modulo n, decrypts to 15. So an attacker can multiply a plaintext by any value they choose without reading it: the run takes the ciphertext of a payment of 500, multiplies it by the ciphertext of 2, and the receiver decrypts 1000. No key, no factoring, and the receiver sees a perfectly valid ciphertext.

Failure 3: a small message with a small exponent. With e of 3 and M of 42, the ciphertext is 74,088, which is 42 cubed and far below the modulus, so the integer cube root is the message.

And what padding does. The same four-byte message PASS padded twice gives two different blocks, so two different ciphertexts, which kills failure 1. The structure 00 02 then at least eight non-zero random bytes then 00 then the message does two jobs: it lets the receiver find where the message begins, and it lets the receiver reject a block that does not have that shape, which kills failure 2, because the product of two valid blocks is not itself a valid block. And because the padded value fills the modulus, M cubed exceeds n and failure 3 is gone.

The two schemes RFC 8017 specifies

RSAES-PKCS1-v1_5. The scheme above: 00 02, at least eight non-zero random bytes, 00, then the message. Simple, in use everywhere, and with a history.

Bleichenbacher's attack, 1998. A server that reports why a decryption failed, distinguishing "the padding was wrong" from "the padding was right but the contents were rejected", leaks one bit per query. With enough queries, of the order of a million, an attacker recovers the plaintext of a captured ciphertext without the private key. The defect is not in the padding but in the error reporting, and the mitigation is to make every failure indistinguishable, which is genuinely hard to get right.

RSAES-OAEP, Optimal Asymmetric Encryption Padding. A construction using a hash function and a mask generation function which, with a proof under reasonable assumptions, is resistant to chosen ciphertext attack. RFC 8017 recommends it for new applications and keeps v1.5 for compatibility with deployed systems.

munotes.in226

Textbook RSA Is Not RSA: Why Padding Exists

And the rule that covers both: signature padding is a different scheme from encryption padding. RFC 8017 specifies RSASSA-PKCS1-v1_5 and RSASSA-PSS for signatures, and using an encryption padding for a signature, or the reverse, is a real vulnerability rather than an untidiness.

How to write the practical, so that it earns the marks

Aim. Implement RSA encryption and decryption, and demonstrate three security considerations of the raw scheme.

Method.

  1. Generate p and q with a primality test, and print the test result.
  2. Compute n, phi(n), choose e, compute d by the extended Euclidean algorithm, and print e d modulo phi(n) to show it is 1.
  3. Encrypt and decrypt a message and show it recovered.
  4. Encrypt the same message twice and show the ciphertexts identical: determinism.
  5. Multiply two ciphertexts and show the product decrypting to the product of the plaintexts: malleability.
  6. With e of 3 and a small message, take an integer cube root of the ciphertext and recover the message with no key.
  7. Add PKCS #1 v1.5 padding and repeat step 4, showing the ciphertexts now differ.

Conclusion. The arithmetic is correct and the raw scheme is not a cipher. Name OAEP as the modern answer, and say that a real implementation uses a reviewed library rather than this code.

And the honest sentence to put in the journal: this implementation is for learning. Real RSA needs constant-time arithmetic, blinding, a vetted random source and a reviewed padding implementation, and none of those is in a student exercise.

Distinctions that carry marks

Textbook RSAPadded RSA
Same message, same ciphertextyesno
Product of ciphertexts is meaningfulyesno, the result fails the padding check
A short message is safenoyes, the block fills the modulus
Suitable for useneveryes
PKCS #1 v1.5 encryption paddingOAEP
Shape00 02, random non-zero bytes, 00, messagehash-based mask generation
Chosen ciphertext resistancevulnerable if errors are distinguishable (Bleichenbacher, 1998)resistant, with a proof
RFC 8017 saysretained for compatibilityrecommended for new applications
Encryption paddingSignature padding
Schemes in RFC 8017RSAES-PKCS1-v1_5, RSAES-OAEPRSASSA-PKCS1-v1_5, RSASSA-PSS
Using one for the othera vulnerability, not an untidiness

What beginners get wrong here

Submitting the formula as the implementation. It is the arithmetic, not the cipher.

Believing a working round trip means a working cipher. It means the arithmetic is consistent. The three failures in this chapter all pass a round-trip test.

Typing primes. Generate them and test them. The first version of this chapter's own listing typed two numbers that were not prime, and it ran.

munotes.in227

Textbook RSA Is Not RSA: Why Padding Exists

Using e of 3 in an exercise and drawing conclusions from it. It is the value that makes failures 3 and Hastad's attack work.

Writing your own padding for real use. Use a reviewed library. The journal can implement v1.5 to show the idea and should say that the real thing is not student work.

Quick revision

  • Textbook RSA is not a cipher. Three failures: deterministic, malleable, and breakable on a short message with a small exponent.
  • Determinism: the attacker encrypts guesses with the public key and compares. Proved with a two-entry PASS and FAIL dictionary.
  • Malleability: c(3) c(5) decrypts to 15, and c(500) c(2) decrypts to 1000, with no key.
  • Small message: with e of 3 and M of 42, the ciphertext is 74,088 and its integer cube root is 42.
  • PKCS #1 v1.5 encryption padding: 00 02, at least eight non-zero random bytes, 00, message. The same message padded twice gives different blocks, so different ciphertexts.
  • Bleichenbacher, 1998: a server that says WHY a decryption failed leaks one bit per query and the plaintext follows. The fault is the error reporting.
  • OAEP is what RFC 8017 recommends for new applications; v1.5 is retained for compatibility.
  • Signature padding is a different scheme from encryption padding.
  • Generate and TEST primes; never type them.

Test yourself

1. Name the three failures of textbook RSA. It is deterministic, so the same message always gives the same ciphertext and an attacker can encrypt guesses with the public key and compare. It is malleable, so multiplying two ciphertexts gives a ciphertext of the product of the plaintexts. And a short message with a small public exponent can be recovered by taking an integer root of the ciphertext, because the power does not exceed the modulus.

2. Show how determinism is exploited when a field has few possible values. The attacker knows the format, so they know the field is, say, PASS or FAIL. Using the public key, which is public, they encrypt both possibilities and build a two-entry table of ciphertexts. Comparing a captured ciphertext against the table reveals the value at once, with no key and no cryptanalysis.

3. Demonstrate RSA's malleability. Let c1 be the ciphertext of m1 and c2 of m2. Then c1 c2 modulo n is (m1 m2) to the power e modulo n, so it decrypts to m1 * m2. In the chapter's run the ciphertexts of 3 and 5 multiplied to a ciphertext that decrypted to 15, and the ciphertext of a payment of 500 multiplied by the ciphertext of 2 decrypted to 1000.

munotes.in228

Textbook RSA Is Not RSA: Why Padding Exists

4. Describe PKCS #1 v1.5 encryption padding and say what each part does. The block is 00, then 02, then at least eight random non-zero bytes, then a 00 separator, then the message, filling the modulus. The random bytes make the block different every time, which removes determinism. The 00 separator lets the receiver find where the message begins. The fixed shape lets the receiver reject a block that does not have it, which removes malleability, and filling the modulus removes the small-message attack.

5. What was Bleichenbacher's attack, and where was the fault? A chosen ciphertext attack of 1998 against PKCS #1 v1.5 encryption padding. A server that distinguishes "the padding was malformed" from "the padding was well formed but the contents were rejected" leaks one bit of information per query, and with of the order of a million adaptively chosen queries an attacker recovers the plaintext of a captured ciphertext without the private key. The fault is in the error reporting rather than in the padding itself, and the mitigation is to make every failure indistinguishable.

6. What does RFC 8017 recommend for new applications, and what does it retain? It recommends RSAES-OAEP for new applications, because it is resistant to chosen ciphertext attack with a proof under reasonable assumptions. It retains RSAES-PKCS1-v1_5 for compatibility with systems already deployed.

7. Why must primes in an RSA implementation be generated and tested rather than typed? Because a number that merely looks like a prime may not be one, and if p or q is composite then phi(n) as computed is wrong, so d is not a true inverse of e and decryption fails for some messages while working for others. The program will still run and exit successfully, so nothing reports the error; the first version of this chapter's own listing had exactly that defect.

Contents This chapter on its own page

munotes.in229

Chapter Thirty-Eight

Distributing a Public Key: Announcement, Directory, Authority, Certificate

Syllabus topic Module 1, "Key Management: Public-Key Cryptosystems"

In one line

A public key is useless unless you know whose it is. The whole of key management for public keys is the problem of binding a key to an identity, and there are four answers of increasing strength.

In the wording a student can write in an examination: the distribution of public keys is the problem of making a party's public key available to others in a way that assures them the key really belongs to that party. Four approaches are used, in increasing order of the trust they establish: public announcement, a publicly available directory, a public-key authority, and public-key certificates.

Why this is a problem at all

A public key is public, so there is no confidentiality to protect. The problem is entirely one of authenticity: if Darth can convince Asha that Darth's public key is Bharat's, then Asha encrypts to Darth, Darth reads everything, and re-encrypts onward to Bharat. That is the same attack the Diffie-Hellman chapter will perform, in a different dress.

So the security of a public-key system reduces to the authenticity of its public keys, and that is why a third of Module 2 is about certificates. An answer that says "public keys need no protection because they are public" has missed the topic entirely.

Scheme 1: public announcement

Anybody may broadcast their public key: append it to every message, post it to a mailing list, put it on a web page.

What it gives. Convenience, and nothing else. It requires no infrastructure at all.

The attack that kills it. Forgery. Anybody can announce a key claiming to be somebody else. Darth publishes a key in Bharat's name, and everybody who believes the announcement encrypts to Darth. Darth can read those messages until Bharat notices and repudiates the forgery, and by then the damage is done.

So it fails, and the failure is the reason for scheme 2.

Scheme 2: a publicly available directory

A trusted organisation maintains a directory of name and public-key entries. Parties register in person or by some authenticated means; the directory is published; parties may replace their entries; and the directory is accessible electronically.

What it gives. A greater degree of security, because the entries were registered rather than merely announced.

The attack that kills it. Compromise of the directory itself. If an opponent obtains the directory's private key, or its write access, they can issue forged entries and impersonate anybody. Worse, the directory is consulted electronically, so the responses themselves must be authenticated, and if they are not, tampering with a response is as good as tampering with the directory.

So it fails, and the failure is the reason for scheme 3.

munotes.in230

Distributing a Public Key: Announcement, Directory, Authority, Certificate

Scheme 3: a public-key authority

Stronger control: a central authority holds the directory and every party knows the authority's own public key in advance. Only the authority knows the corresponding private key. To obtain somebody's public key you ask the authority, and it replies with a message signed with its private key, containing the requested key, the original request and a timestamp.

What it gives. Now the response is authenticated. A tampered reply fails the signature check, and the timestamp shows the reply is current rather than a replay of an old one. The classical exchange has seven messages, because both parties fetch each other's key and then run a nonce exchange to prove liveness.

The problems that kill it. Two, and both are practical rather than cryptographic.

A bottleneck. The authority must be contacted for every key, by every party, before every conversation. It becomes a single point of failure and a performance limit.

Still a single point of compromise. The authority's records must be kept secure, and the authority must be online.

So it fails, and the failure is the reason for scheme 4.

Scheme 4: public-key certificates

The idea that removes the bottleneck. A certificate consists of a public key, an identifier of the key's owner, and a validity period, all signed by a trusted third party called a certification authority. The owner then hands the certificate to anybody who needs their key.

Why this works. The recipient verifies the signature using the certification authority's public key, which they already have. If it verifies, the binding between the name and the key is as trustworthy as the certification authority. And no online contact with the authority was needed, because the signature travels with the key.

Kohnfelder's two requirements, which are what an examination asks for.

  1. Any participant can read a certificate to determine the name and public key of its owner.
  2. Any participant can verify that the certificate originated from the certification authority and is not counterfeit.

And two more that practice added, and that a complete answer includes.

  1. Only the certification authority can create and update certificates, which follows from its private key being secret.
  2. Any participant can verify the currency of the certificate, which is why certificates carry a validity period and why revocation exists. This is the requirement that is hardest to meet, and Module 2's chapter on revocation is about how badly it is met in practice.

Worked example: the four schemes against one attacker

The setting. Asha in the accounts office needs Bharat's public key at the University to send an encrypted payment file. Darth is on the network and wants to read it.

munotes.in231

Distributing a Public Key: Announcement, Directory, Authority, Certificate

Under public announcement. Bharat has posted his key on the department's web page. Darth alters the page, or simply emails Asha a key in Bharat's name. Darth wins, and the only defence is Asha independently knowing Bharat's key, which is the problem she was trying to solve.

Under a directory. Asha queries the directory. Darth cannot forge an entry, but he intercepts the reply and substitutes his own key, because the reply is not signed. Darth wins, and the lesson is that a directory must authenticate its answers, not only its entries.

Under an authority. Asha queries the authority, which replies with Bharat's key, Asha's original request and a timestamp, all signed with the authority's private key. Darth cannot forge the signature and cannot replay an old reply, because the timestamp is checked and the request is echoed back. Asha wins. The cost is that the authority had to be online and had to be asked.

Under certificates. Bharat sends Asha his certificate, once, perhaps months earlier. Asha checks the certification authority's signature on it with the authority's public key, which came with her operating system. Asha wins, and nobody contacted anybody. The remaining risk is that Bharat's key has been compromised since the certificate was issued, which is the revocation problem.

The step that carries the marks. Naming the attack at each stage. Forgery defeats announcement; an unsigned reply defeats a directory; nothing defeats an authority except its own availability and compromise; and what remains against certificates is currency, not authenticity.

Distinctions that carry marks

AnnouncementDirectoryAuthorityCertificate
Infrastructurenonea maintained directoryan online signing authorityan offline signing authority
Must be contacted per usenoyesyesno
Replies authenticatednot applicablenoyes, signedsignature travels with the key
Defeated byforgerytampering with a reply, or compromise of the directoryits own unavailability or compromisea compromised key before its certificate expires
Bottlenecknonesomeyesnone
What Module 2 calls itX.509, and the PKI
The problem with a public keyThe problem with a secret key
Isauthenticity: whose key is itconfidentiality: keeping it secret
Solved bycertificates and signaturessecure channels and key hierarchies
Failure modeyou encrypt to the wrong partythe opponent reads everything

What beginners get wrong here

Saying public keys need no protection. They need no secrecy. They need authenticity, which is a harder problem, and it is the topic.

Confusing a directory with an authority. A directory publishes entries; an authority signs its replies. The signature is the whole difference.

Thinking a certificate removes trust. It moves it. You now trust the certification authority instead of trusting the network, which is a better bargain and not the same as trusting nobody.

munotes.in232

Distributing a Public Key: Announcement, Directory, Authority, Certificate

Forgetting the currency requirement. A certificate is a statement made at a point in time. Requirement 4 is why validity periods and revocation exist, and it is the one that fails most often in practice.

Giving only Kohnfelder's two requirements. Give four: readable, verifiable, only the authority can issue, and currency can be checked.

Quick revision

  • The problem with a public key is authenticity, not secrecy: whose key is it.
  • Four schemes, each answering the previous one's failure: public announcement (killed by forgery), a public directory (killed by an unsigned reply or a compromised directory), a public-key authority (works, but is an online bottleneck and a single point of compromise), certificates (no online contact needed).
  • An authority's reply carries the requested key, the original request and a timestamp, signed with the authority's private key.
  • A certificate is a public key, an owner identifier and a validity period, signed by a certification authority.
  • Requirements: readable by any participant; verifiable as genuine; only the authority can issue or update; and its currency can be checked.
  • The remaining risk with certificates is currency, not authenticity, which is why revocation exists.
  • Certificates are taken up in Module 2 as X.509 and the public-key infrastructure.

Test yourself

1. Why does a public key need protection when it is public? Because the problem is authenticity rather than secrecy. If an attacker can make a recipient believe that the attacker's key belongs to somebody else, the recipient encrypts to the attacker, who reads the message and may re-encrypt it onward. The security of the whole public-key system therefore rests on the authenticity of its public keys.

2. Name the four approaches to public-key distribution, in order. Public announcement; a publicly available directory; a public-key authority; and public-key certificates.

3. What defeats public announcement, and what defeats a directory? Public announcement is defeated by forgery: anybody can broadcast a key in somebody else's name. A directory is defeated either by compromise of the directory itself, or, more simply, by tampering with a reply, since the directory's answers are not themselves authenticated.

4. What does a public-key authority's reply contain, and why each part? The requested public key, so the enquirer obtains it; the original request, so the enquirer can confirm the reply matches what was asked and was not substituted; and a timestamp, so that an old reply cannot be replayed. The whole message is signed with the authority's private key, so that none of it can be altered.

5. What are the two disadvantages of a public-key authority? It must be contacted for every key by every party, so it is a bottleneck and a single point of failure; and it must be online and its records secure, so it remains a single point of compromise.

munotes.in233

Distributing a Public Key: Announcement, Directory, Authority, Certificate

6. Define a public-key certificate and state the requirements it must satisfy. A certificate is a public key together with an identifier of its owner and a validity period, all signed by a trusted certification authority. Any participant must be able to read it to determine the owner and the key; any participant must be able to verify that it came from the certification authority and is not counterfeit; only the certification authority may create or update certificates; and any participant must be able to verify that the certificate is current.

7. What does a certificate not solve? Currency. A certificate asserts a binding at the time of issue, so a key compromised afterwards remains apparently valid until the certificate expires or is revoked. Revocation lists and online status checks exist for this, and they are the weakest part of the arrangement in practice.

Contents This chapter on its own page

munotes.in234

Chapter Thirty-Nine

Key Management for Secret Keys: Session Keys and the Key Hierarchy

Syllabus topic Module 1, "Key Management: Key Management"

In one line

Use a long-lived key only to distribute short-lived ones. The long-lived key is hard to deliver and is therefore delivered rarely; the short-lived key is used for one conversation and thrown away.

In the wording a student can write in an examination: key management for symmetric cryptography distinguishes two kinds of key. A session key is used to encrypt the data of a single logical connection and is discarded when the connection ends. A permanent key, or master key, is used only to distribute session keys, between a pair of parties or between a party and a key distribution centre. The arrangement is called a key hierarchy, and its purpose is that the key which is difficult to distribute is distributed as seldom as possible.

The problem the hierarchy solves

The symmetric cipher chapter established that N parties talking pairwise need N(N-1)/2 keys, and that every one of them must be delivered over a secure channel. For a hundred parties that is 4,950 secure deliveries, and for a thousand it is 499,500.

The hierarchy changes the count. Each party shares one master key with a key distribution centre. When two parties want to talk, the centre issues them a fresh session key, encrypted under each party's master key. So:

  • Master keys needed: N, one per party, each delivered once, by whatever secure means is available.
  • Session keys needed: as many as there are conversations, each generated on demand and never delivered by hand at all.

That is the trade: the hard problem is solved N times instead of N(N-1)/2 times, and the easy problem is solved as often as necessary. The cost is that the centre must be trusted, because it knows every master key and sees every session key.

The key distribution centre, message by message

The classical scheme is due to Needham and Schroeder, 1978. Asha wants to talk to Bharat; the centre is C; Asha shares master key Ka with C and Bharat shares Kb.

Message 1, Asha to C. Asha's identity, Bharat's identity, and a nonce N1, which is a number used once, to tie the reply to this request.

Message 2, C to Asha, encrypted under Ka. The new session key Ks; a copy of the original request, so Asha can see it was not altered; the nonce N1, so Asha knows the reply is fresh; and a ticket for Bharat, being Ks and Asha's identity encrypted under Kb.

Message 3, Asha to Bharat. The ticket, which Asha cannot read and cannot alter.

Message 4, Bharat to Asha, encrypted under Ks. A nonce N2.

Message 5, Asha to Bharat, encrypted under Ks. A function of N2, conventionally N2 minus 1.

munotes.in235

Key Management for Secret Keys: Session Keys and the Key Hierarchy

What each part is for. Messages 1 and 2 obtain the key and prove the reply is fresh and unaltered. Message 3 delivers the key to Bharat in a form only he can open. Messages 4 and 5 are the part students omit, and they prove that Asha is actually present and holds Ks, rather than replaying an old message 3.

And messages 4 and 5 are not enough. The protocol has a replay flaw, found by Denning: an attacker who has recorded an old session can replay message 3 and complete 4 and 5 using the old session key, which they recovered later. Module 2's chapter on authentication protocols works it, and the fix is a timestamp.

Why a key has a lifetime

A question asking "why should a session key be changed frequently" wants two reasons, and they are different reasons.

Reason 1: the more ciphertext under one key, the more an attacker has to work with. Every cryptanalytic attack in this module needs data: differential cryptanalysis needs 2 to the power 47 chosen plaintexts, the 64-bit block's birthday bound arrives after 2 to the power 32 blocks, and the 3DES chapter's own standard limits one key bundle to 2 to the power 20 blocks, which is eight mebibytes. Changing the key resets the counter.

Reason 2: the longer a key exists, the more chance it is exposed. Not by cryptanalysis but by accident: a memory dump, a backup, a log file, a departing employee, a stolen laptop. And the damage from an exposed key is bounded by what it protected, so a key that protected one connection for ten minutes is a far smaller loss than one that protected everything for a year.

Those two reasons pull in the same direction and are answered by the same measure, but they are not the same argument, and a full answer gives both.

The counter-pressure. Every key change costs a distribution, and a distribution costs messages and delay. So the lifetime is a trade-off: for a connection-oriented protocol, one key per connection, with a new key if the connection is very long-lived; for a connectionless protocol, a new key at intervals or after a volume of data.

The rest of key management, in one list

MU's topic is one label and it covers more than the hierarchy. These are the parts a complete answer names.

Generation. A key must be unpredictable, so it must come from a source with real entropy, not from a program's default random generator. The commonest catastrophic failure in this whole subject is a predictable key, and the shared-prime attack in the RSA chapter is one instance of it.

munotes.in236

Key Management for Secret Keys: Session Keys and the Key Hierarchy

Distribution. The hierarchy above, or public-key methods, or Diffie-Hellman.

Storage. A key at rest is protected by another key, or by hardware. The chain has to end somewhere, and where it ends is either a hardware security module or a password a human remembers.

Use. One key, one purpose. A key used both to encrypt and to authenticate can leak through the interaction of the two uses, so separate keys are derived for separate purposes, normally from one master by a key derivation function.

Lifetime and destruction. A key is retired at the end of its lifetime, and the copies are destroyed. A key that is "no longer used" but still present in a backup is still a live secret.

Recovery. If a key is lost, the data under it is lost. So an organisation either accepts that or keeps an escrowed copy, and an escrowed copy is a second place the key can be stolen from. There is no arrangement that is both recoverable and unrecoverable, and pretending otherwise is the commonest error in a policy answer.

Worked example: a college's key management, designed

The requirement. A college with 40 departments submits internal marks to the University over the network. Each submission must be confidential and authenticated, and a compromised department must not expose the others.

Step 1: do not give every pair a key. 40 departments and the University is 41 parties, which pairwise would be 820 keys. And they do not need to talk pairwise: every department talks only to the University.

Step 2: one master key per department. 40 master keys, each delivered once, in person, when the department is enrolled. The University holds all 40; each department holds one. A compromised department exposes its own key and no other.

Step 3: a fresh session key per submission. The department requests one, the University issues it encrypted under that department's master key, and it is used for that submission only. Nothing is delivered by hand after step 2.

Step 4: separate keys for encryption and authentication. Derive two keys from the session key by a key derivation function, one for the cipher and one for the message authentication code. Never use the same key for both.

Step 5: a lifetime and a limit. The session key expires when the submission completes, and in any case after the volume limit of the cipher in use. With AES-128 in counter mode that limit is far beyond one submission, so the practical rule is one key per submission.

Step 6: storage and destruction. Master keys in a hardware module at the University and in an encrypted store at each department. Session keys in memory only, and zeroed when the submission ends.

munotes.in237

Key Management for Secret Keys: Session Keys and the Key Hierarchy

Step 7: recovery, decided explicitly. Marks submissions must be readable years later for audit, so the archive is re-encrypted under a long-lived archival key held by the University, and that key is escrowed with the Registrar. The escrow is written into the policy rather than improvised, because step 7 is where a design that is otherwise sound usually fails.

The step that carries the marks. Steps 2 and 3 together are the hierarchy; step 4 is the rule nobody remembers; step 7 is the one nobody writes down.

Distinctions that carry marks

Session keyMaster key
Lifetimeone connection, or one messagemonths or years
How distributedby the master key, electronicallyonce, by a secure physical or out-of-band means
How manyas many as there are conversationsone per party
Storedin memory, zeroed after usein hardware or an encrypted store
If compromisedone conversation is lostevery session key it ever issued
Key distribution centrePublic-key certificate
Must be onlineyesno
Knows every party's secretyesno, it signs public keys only
A compromise of it exposeseverythingthe ability to issue false bindings from then on
Used inKerberosTLS, S/MIME, IPsec with certificates
Reason 1 for a short lifetimeReason 2
Isless data under one key, so less for cryptanalysisless time for the key to leak by accident
Bounded bythe cipher's data limitoperational exposure
Answered byrekeying on volumerekeying on time

What beginners get wrong here

Saying a session key is "more secure" than a master key. It is not. It is shorter-lived, so the damage from losing it is smaller and the data under it is less.

Forgetting that the key distribution centre knows everything. It is the price of the hierarchy, and Kerberos in Module 2 pays it.

Using one key for encryption and authentication. Derive two. This is a real vulnerability, not tidiness.

Giving one reason for limiting a key's lifetime. Give both: less data for cryptanalysis, and less time to leak.

Treating key recovery as a detail. An escrowed key is a second place to steal from, and a system with no escrow loses data permanently. The choice must be made explicitly.

Quick revision

  • Session key: one connection, generated on demand, discarded. Master key: long-lived, used only to distribute session keys.
  • The hierarchy's purpose: the key that is hard to distribute is distributed as seldom as possible. N master keys instead of N(N-1)/2.
  • Key distribution centre: each party shares one master key with it; it issues session keys on request. Its cost is that it knows every secret.
  • Needham and Schroeder, five messages: request with a nonce; the centre's signed reply with the session key, the echoed request, the nonce and a ticket for the other party; the ticket delivered; and a nonce exchange to prove the requester is live. It has a replay flaw, fixed by a timestamp.
  • Two reasons for a short lifetime: less data under one key for cryptanalysis, and less time for the key to be exposed by accident.
  • Key management also covers generation (real entropy), storage, use (one key one purpose), destruction, and recovery.
  • Never use one key for both encryption and authentication; derive two.
  • Escrow is a second place a key can be stolen from, and no escrow means lost data is lost. Decide explicitly.
munotes.in238

Key Management for Secret Keys: Session Keys and the Key Hierarchy

Test yourself

1. Distinguish a session key from a master key and say what the hierarchy achieves. A session key encrypts the data of a single logical connection and is discarded at the end of it. A master key is long-lived and is used only to distribute session keys. The hierarchy means the key that is difficult to distribute, the master key, is distributed only once per party, while session keys, which are easy to generate and deliver electronically, are used for the actual traffic. The number of secure physical deliveries falls from N(N-1)/2 to N.

2. Describe the five messages of the Needham and Schroeder key distribution. Asha sends the centre her identity, Bharat's identity and a nonce. The centre replies, encrypted under Asha's master key, with the session key, a copy of the original request, the nonce, and a ticket containing the session key and Asha's identity encrypted under Bharat's master key. Asha forwards the ticket to Bharat. Bharat sends Asha a nonce encrypted under the session key. Asha returns a function of that nonce, conventionally one less, encrypted under the session key.

3. What are messages 4 and 5 for? To prove that Asha is present and holds the session key, rather than an attacker replaying an old message 3. Bharat's nonce cannot be answered correctly by anybody who does not hold the session key at that moment.

4. Give two distinct reasons for limiting the lifetime of a key. First, the more data encrypted under a single key, the more material a cryptanalyst has and the closer the cipher comes to its own data limits, such as the birthday bound of a 64-bit block. Second, the longer a key exists the greater the chance it is exposed by non-cryptographic means such as a backup, a log or a stolen device, and the damage is bounded by what the key protected.

5. What is the principal disadvantage of a key distribution centre? It knows every party's master key and sees every session key, so a compromise of the centre compromises everything, and it must be online and reachable for any two parties to begin talking.

munotes.in239

Key Management for Secret Keys: Session Keys and the Key Hierarchy

6. Why should encryption and authentication use different keys? Because using one key for two algorithms can allow the interaction between them to leak information that neither would leak alone, and because it removes the ability to change one without the other. The standard practice is to derive two separate keys from a single session key using a key derivation function.

7. What is the dilemma about key recovery? That a system with no escrowed copy of a key loses the protected data permanently if the key is lost, while a system with an escrowed copy has created a second place from which the key can be stolen. There is no arrangement that is both recoverable and unrecoverable, so the choice must be made explicitly in a policy rather than left to chance.

Contents This chapter on its own page

munotes.in240

Chapter Forty

Diffie-Hellman Key Exchange

Syllabus topic Module 1, "Key Management: Diffie-Hellman Key Exchange"

In one line

Both parties raise a public number to a secret power and exchange the results; each then raises the other's result to their own secret power, and both get the same answer. An eavesdropper who sees both public results cannot compute it.

In the wording a student can write in an examination: the Diffie-Hellman key exchange, published in 1976, allows two parties who have never met to agree a shared secret over a public channel. Two public values are fixed: a large prime p and an integer g which is a primitive root of p. Each party chooses a secret integer, computes a public value, and exchanges it:

Alice: A = g to the power a mod p

Bob: B = g to the power b mod p

Alice computes K = B to the power a mod p

Bob computes K = A to the power b mod p

Both results equal g to the power ab modulo p. The exchange is secure because an opponent holding p, g, A and B must solve the discrete logarithm problem to recover a or b.

Why it was the breakthrough

The key-management chapter left one problem unsolved. A key distribution centre works, but it must be trusted with every secret and must be online. Public-key encryption works, but requires the sender to have obtained the receiver's authentic public key first.

Diffie-Hellman needs neither. Two parties with no prior relationship, no shared secret and no third party can agree a key over a channel an opponent is reading. That is the thing symmetric cryptography cannot express at all, and it is why the 1976 paper is the start of the subject's modern half.

And note what it does not do. It agrees a key. It does not say with whom, and it encrypts nothing. Both of those are the next chapter.

Why the generator must be a primitive root

A primitive root of a prime p is an integer g whose powers run through every non-zero residue modulo p before repeating. Formally its order, the smallest k with g to the power k congruent to 1, is p - 1.

That matters because the shared secret is g to the power ab, and the number of possible shared secrets is the number of values g can reach. If g reaches only a few of them, the opponent has only a few to try.

The run below shows it modulo 11, where the powers of 2 reach all ten non-zero residues, the powers of 3 reach only five, and the powers of 10 reach only two. A generator of order 2 gives an exchange with two possible keys.

munotes.in241

Diffie-Hellman Key Exchange

The exchange, run

# Diffie-Hellman key exchange, and the discrete logarithm problem under it.

def order(g, p):
    x, k = g % p, 1
    while x != 1:
        x = (x * g) % p
        k += 1
    return k

print("a primitive root generates every non-zero residue. Modulo 11:")
for g in (2, 3, 10):
    powers = [pow(g, a, 11) for a in range(1, 11)]
    print("   g = %2d  powers: %s  order %2d  primitive root: %s"
          % (g, " ".join("%2d" % v for v in powers), order(g, 11), order(g, 11) == 10))
print("   2 reaches all ten non-zero residues; 3 reaches only five; 10 only two.")
print("   Only a primitive root gives the exchange the full key space.")
print()

print("the primitive roots of 11 are:",
      [g for g in range(2, 11) if order(g, 11) == 10])
print()

p, g = 353, 3
a, b = 97, 233
print("the standard worked exchange: p = %d, g = %d" % (p, g))
print("   g is a primitive root of p:", order(g, p) == p - 1)
A = pow(g, a, p)
B = pow(g, b, p)
print("   Alice picks a = %d in secret, and sends A = g^a mod p = %d" % (a, A))
print("   Bob   picks b = %d in secret, and sends B = g^b mod p = %d" % (b, B))
print("   Alice computes B^a mod p = %d" % pow(B, a, p))
print("   Bob   computes A^b mod p = %d" % pow(A, b, p))
print("   they agree:", pow(B, a, p) == pow(A, b, p))
print("   and both values are g^(ab) mod p =", pow(g, a * b, p))
print()

print("what the opponent has: p, g, A and B. To get the secret they must find")
print("a from A, which is the DISCRETE LOGARITHM problem. Here p is 353, so:")
for target, name in ((A, "A"), (B, "B")):
    for x in range(1, p):
        if pow(g, x, p) == target:
            print("   log base %d of %3d mod %d = %3d   (found by trying %d values)"
                  % (g, target, p, x, x))
            break
print("   That works because p is 353. For a 2048-bit p the best known methods")
print("   are as hard as factoring a modulus of the same size.")
print()

print("every pair of private values gives a shared secret, and the exchange")
print("never needs them to have met:")
for aa, bb in ((5, 7), (100, 200), (351, 2)):
    sa = pow(pow(g, bb, p), aa, p)
    sb = pow(pow(g, aa, p), bb, p)
    print("   a = %3d, b = %3d -> both derive %3d, agree: %s" % (aa, bb, sa, sa == sb))
print()

print("RFC 3526's 2048-bit group (id 14), used for real, so the arithmetic scales.")
print("The constant is CHECKED before it is used, not trusted:")
P2048 = int(
 "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
 "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
 "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
 "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
 "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
 "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
 "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
 "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
 "3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFF"
 "FFFFFFFF".replace(" ", ""), 16)
def is_prime(n):
    d, r = n - 1, 0
    while d % 2 == 0:
        d //= 2
        r += 1
    for a in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
        x = pow(a, d, n)
        if x in (1, n - 1):
            continue
        for _ in range(r - 1):
            x = (x * x) % n
            if x == n - 1:
                break
        else:
            return False
    return True

print("   the constant has %d bits           :" % P2048.bit_length(),
      P2048.bit_length() == 2048)
print("   it is prime                        :", is_prime(P2048))
print("   and (p - 1) / 2 is prime, so it is a SAFE prime:",
      is_prime((P2048 - 1) // 2))
print("   the generator 2 therefore has order (p - 1) / 2, which is the largest")
print("   order a safe prime allows, so the key space is very nearly the whole group.")
print()

import random
random.seed(9)
a2 = random.getrandbits(256)
b2 = random.getrandbits(256)
A2 = pow(2, a2, P2048)
B2 = pow(2, b2, P2048)
print("   p has %d bits, g = 2" % P2048.bit_length())
print("   private exponents of %d bits each" % a2.bit_length())
print("   A begins %s..." % hex(A2)[2:18])
print("   B begins %s..." % hex(B2)[2:18])
s1, s2 = pow(B2, a2, P2048), pow(A2, b2, P2048)
print("   shared secret begins %s... and both sides agree: %s"
      % (hex(s1)[2:18], s1 == s2))
print("   a 2048-bit secret is then HASHED down to a symmetric key; it is never")
print("   used as a key directly.")
munotes.in242

Diffie-Hellman Key Exchange

a primitive root generates every non-zero residue. Modulo 11:
   g =  2  powers:  2  4  8  5 10  9  7  3  6  1  order 10  primitive root: True
   g =  3  powers:  3  9  5  4  1  3  9  5  4  1  order  5  primitive root: False
   g = 10  powers: 10  1 10  1 10  1 10  1 10  1  order  2  primitive root: False
   2 reaches all ten non-zero residues; 3 reaches only five; 10 only two.
   Only a primitive root gives the exchange the full key space.

the primitive roots of 11 are: [2, 6, 7, 8]

the standard worked exchange: p = 353, g = 3
   g is a primitive root of p: True
   Alice picks a = 97 in secret, and sends A = g^a mod p = 40
   Bob   picks b = 233 in secret, and sends B = g^b mod p = 248
   Alice computes B^a mod p = 160
   Bob   computes A^b mod p = 160
   they agree: True
   and both values are g^(ab) mod p = 160

what the opponent has: p, g, A and B. To get the secret they must find
a from A, which is the DISCRETE LOGARITHM problem. Here p is 353, so:
   log base 3 of  40 mod 353 =  97   (found by trying 97 values)
   log base 3 of 248 mod 353 = 233   (found by trying 233 values)
   That works because p is 353. For a 2048-bit p the best known methods
   are as hard as factoring a modulus of the same size.

every pair of private values gives a shared secret, and the exchange
never needs them to have met:
   a =   5, b =   7 -> both derive 250, agree: True
   a = 100, b = 200 -> both derive 187, agree: True
   a = 351, b =   2 -> both derive 157, agree: True

RFC 3526's 2048-bit group (id 14), used for real, so the arithmetic scales.
The constant is CHECKED before it is used, not trusted:
   the constant has 2048 bits           : True
   it is prime                        : True
   and (p - 1) / 2 is prime, so it is a SAFE prime: True
   the generator 2 therefore has order (p - 1) / 2, which is the largest
   order a safe prime allows, so the key space is very nearly the whole group.

   p has 2048 bits, g = 2
   private exponents of 256 bits each
   A begins fd828d32e3b2ee31...
   B begins e5b1fb44f2a8b3ee...
   shared secret begins bc2cbc25f9ffc393... and both sides agree: True
   a 2048-bit secret is then HASHED down to a symmetric key; it is never
   used as a key directly.
munotes.in243

Diffie-Hellman Key Exchange

Read six things out of that run.

The primitive roots of 11 are 2, 6, 7 and 8, and the run lists them. 3 has order 5 and 10 has order 2, so neither is usable. This is the condition made visible.

The standard exchange works. p of 353, g of 3, a of 97 and b of 233 give A of 40, B of 248, and both parties derive 160, which is g to the power ab modulo p. If you meet this example elsewhere those are the numbers.

The discrete logarithm was solved by trying values, 97 of them for A and 233 for B. That is the honest way to present a hard problem: show it being easy at a size where it is easy, and then state the size at which it is not. For a 2,048-bit prime the best known methods are comparable in cost to factoring a modulus of the same size.

Every pair of private values agrees, and the run tries three more pairs including a of 351, which is nearly p - 1. Nothing about the exchange depends on the private values being small or special.

munotes.in244

Diffie-Hellman Key Exchange

The RFC 3526 constant was checked before use: 2,048 bits, prime, and (p - 1) / 2 also prime, so it is a safe prime. A safe prime is chosen precisely so that the generator's order is large: with p - 1 equal to twice a prime, the only possible orders are 1, 2, that prime, and twice it, so a generator of 2 has order at least (p - 1) / 2, which is very nearly the whole group.

And the real exchange works. A 2,048-bit prime, 256-bit private exponents, and both sides derive the same 2,048-bit secret. The secret is then hashed down to a symmetric key and never used as a key directly, because its bits are not uniformly distributed and because a key must be the size the cipher wants.

Worked example, by hand

Every figure below was recomputed by program before it was written down. An earlier draft of this section did the four exponentiations by hand and got all four wrong, including four of the modular reductions, and the two sides still appeared to agree because the errors were consistent. A Diffie-Hellman answer is checked by computing the secret both ways and comparing.

"Two parties use Diffie-Hellman with q = 71 and a primitive root alpha = 7. Party A's private key is 5 and party B's is 12. Find the shared secret."

Step 1: A's public value, 7 to the power 5 modulo 71. 5 is 101 in binary.

Bit 1 is 1: square 1 to get 1, multiply by 7 to get 7. Bit 2 is 0: square 7 to get 49. 49. Bit 3 is 1: square 49 to get 2,401; 2,401 modulo 71 is 58, since 71 times 33 is 2,343; multiply by 7 to get 406; 406 modulo 71 is 51, since 71 times 5 is 355.

So A is 51.

Step 2: B's public value, 7 to the power 12 modulo 71. 12 is 1100.

Bit 1 is 1: square 1, multiply by 7, giving 7. Bit 2 is 1: square 7 to get 49, multiply by 7 to get 343; 343 modulo 71 is 59, since 71 times 4 is 284. Bit 3 is 0: square 59 to get 3,481; 3,481 modulo 71 is 2, since 71 times 49 is 3,479. Bit 4 is 0: square 2 to get 4.

So B is 4.

Step 3: the secret, A's way. 4 to the power 5 modulo 71. 5 is 101.

Bit 1 is 1: 4. Bit 2 is 0: square to 16. Bit 3 is 1: square 16 to get 256; 256 modulo 71 is 43, since 71 times 3 is 213; multiply by 4 to get 172; 172 modulo 71 is 30, since 71 times 2 is 142.

munotes.in245

Diffie-Hellman Key Exchange

The secret is 30.

Step 4: the secret, B's way. 51 to the power 12 modulo 71. 12 is 1100.

Bit 1 is 1: 51. Bit 2 is 1: square 51 to get 2,601; 2,601 modulo 71 is 45, since 71 times 36 is 2,556; multiply by 51 to get 2,295; 2,295 modulo 71 is 23, since 71 times 32 is 2,272. Bit 3 is 0: square 23 to get 529; 529 modulo 71 is 32, since 71 times 7 is 497. Bit 4 is 0: square 32 to get 1,024; 1,024 modulo 71 is 30, since 71 times 14 is 994.

The secret is 30, by both routes.

The step that carries the marks. Steps 3 and 4 together. Computing the secret once demonstrates arithmetic; computing it both ways and getting the same value demonstrates the property the exchange exists for, and it is also the only check available on four exponentiations done under time pressure.

What the opponent has, and what they must do

The opponent sees p, g, A and B. They want g to the power ab.

They cannot multiply A by B. That gives g to the power a + b, which is not the secret.

They cannot take A to the power B. That is g to the power a times B, which is meaningless here.

They must recover a from A, or b from B. That is the discrete logarithm problem: given g, p and g to the power a modulo p, find a. There is no known efficient method for a large prime, and the best methods are comparable to factoring.

Strictly, the opponent's task is the Diffie-Hellman problem, computing g to the power ab from g to the power a and g to the power b, which is not known to be exactly as hard as the discrete logarithm problem although no easier method is known. A complete answer distinguishes the two and says that in practice they are treated as equivalent.

Distinctions that carry marks

Diffie-HellmanRSA
Doesagrees a shared secretencrypts, and signs
Hard problemdiscrete logarithmfactoring
Public valuesp, g, and each party's g to the power of its secretn and e
Gives confidentiality directlynoyes
Gives authenticationnoyes, by signing
Needs a prior authentic public keynoyes, for encryption
munotes.in246

Diffie-Hellman Key Exchange

A primitive rootA non-primitive generator
Orderp - 1a proper divisor of p - 1
Residues reachedall non-zeroonly a subgroup
Key spacep - 1 valuesas small as the order
Modulo 112, 6, 7, 83 has order 5; 10 has order 2
Discrete logarithm problemDiffie-Hellman problem
Giveng, p, g to the power ag, p, g to the power a, g to the power b
Findag to the power ab
Relationshipsolving it solves the othernot known to be equally hard, but no easier method is known

What beginners get wrong here

Sending the private value. a and b never leave their owners. Only g to the power a and g to the power b are transmitted.

Thinking the shared secret is g to the power a + b. It is g to the power ab. The exponents multiply because each party raises the other's value to their own exponent.

Saying Diffie-Hellman encrypts. It does not. It agrees a key, which a cipher then uses.

Ignoring the primitive root condition. A generator of small order gives a small key space, and the run shows a generator of order 2.

Using the raw shared secret as a key. It must be hashed to the length the cipher wants, by a key derivation function.

Saying it is secure, full stop. It is secure against a passive eavesdropper. Against an active attacker it fails completely, which is the next chapter.

Quick revision

  • Diffie and Hellman, 1976. Public: a large prime p and a primitive root g. Each party picks a secret, sends g to the power of it, and raises the other's value to their own secret.
  • Both obtain g to the power ab mod p. The exponents multiply.
  • Worked: p 353, g 3, a 97, b 233 gives A 40, B 248, shared secret 160.
  • A primitive root has order p - 1 and reaches every non-zero residue. Modulo 11 they are 2, 6, 7, 8; 3 has order 5 and 10 has order 2.
  • The opponent must solve the discrete logarithm problem. Solved here by trying 97 values, only because p is 353.
  • A safe prime has (p - 1) / 2 prime, so a generator's order is at least (p - 1) / 2. RFC 3526's group 14 is one, and the listing verifies it.
  • The shared secret is hashed to make a key; it is never used as a key directly.
  • Secure against a passive eavesdropper only. It says nothing about who you agreed a key with.
munotes.in247

Diffie-Hellman Key Exchange

Test yourself

1. Set out the Diffie-Hellman exchange. Fix a large prime p and a primitive root g of p, both public. Alice chooses a secret a and sends A equal to g to the power a modulo p; Bob chooses a secret b and sends B equal to g to the power b modulo p. Alice computes B to the power a modulo p and Bob computes A to the power b modulo p; both equal g to the power ab modulo p, which is the shared secret.

2. With q = 71, alpha = 7, a = 5 and b = 12, find the shared secret. A is 7 to the power 5 modulo 71, which is 51. B is 7 to the power 12 modulo 71, which is 4. Alice computes 4 to the power 5 modulo 71, which is 30; Bob computes 51 to the power 12 modulo 71, which is also 30. The shared secret is 30.

3. What is a primitive root, and why must the generator be one? An integer whose powers modulo p run through every non-zero residue before repeating, that is whose order is p - 1. The generator must be one because the shared secret is a power of g, so the number of possible shared secrets is the order of g. A generator of small order gives a correspondingly small key space; modulo 11, the generator 10 has order 2 and would give only two possible keys.

4. What problem must an eavesdropper solve, and why can they not simply combine A and B? They must solve the discrete logarithm problem: recover a from g to the power a modulo p. Multiplying A by B gives g to the power a + b, and the secret is g to the power ab, so no combination of the transmitted values yields it without an exponent.

5. Distinguish the discrete logarithm problem from the Diffie-Hellman problem. The discrete logarithm problem is to find a given g, p and g to the power a modulo p. The Diffie-Hellman problem is to find g to the power ab given g to the power a and g to the power b. Solving the discrete logarithm problem solves the Diffie-Hellman problem; the converse is not known, although no method easier than solving the logarithm is known either, so in practice they are treated as equivalent.

6. What is a safe prime and why is one used? A prime p for which (p - 1) / 2 is also prime. It is used because the possible orders of any element then divide 2 times that prime, so they are 1, 2, the prime, or twice it; a small generator such as 2 therefore has order at least (p - 1) / 2, which guarantees a very large key space without having to verify primitivity separately. RFC 3526's 2048-bit group is a safe prime and the chapter's listing checks that it is.

munotes.in248

Diffie-Hellman Key Exchange

7. Why is the shared secret not used directly as an encryption key? Because it is a residue modulo a large prime, so its bits are not uniformly distributed and it is the wrong length for any cipher. It is passed through a key derivation function, normally hash-based, to produce a key of the required size with the required statistical properties.

Contents This chapter on its own page

munotes.in249

Chapter Forty-One

The Man in the Middle, and Why Diffie-Hellman Needs Authentication

Syllabus topic Module 1, "Key Management: Diffie-Hellman Key Exchange"

In one line

Diffie-Hellman agrees a key with whoever answered. An attacker who can intercept and replace both public values ends with one key shared with each party, and neither party can tell.

In the wording a student can write in an examination: the Diffie-Hellman key exchange is secure against a passive eavesdropper but not against an active attacker. In the man-in-the-middle attack, the attacker intercepts both parties' public values and substitutes their own. Each honest party then completes the exchange with the attacker rather than with the intended party, establishing a shared secret with the attacker. The attacker decrypts everything each party sends, may alter it, and re-encrypts it under the other key. The protocol completes successfully and neither party detects anything wrong, because nothing in the exchange authenticates the origin of the public values.

The attack, step by step before it is run

Step 1. Alice computes A equal to g to the power a and sends it. Darth intercepts it.

Step 2. Darth computes X equal to g to the power x with his own secret x, and sends X to Bob as though it were Alice's.

Step 3. Bob computes B equal to g to the power b and sends it. Darth intercepts it and sends X to Alice as though it were Bob's.

Step 4. Alice computes X to the power a, believing it is a secret shared with Bob. Darth computes A to the power x. Both are g to the power ax. Darth and Alice now share a key.

Step 5. Bob computes X to the power b. Darth computes B to the power x. Both are g to the power bx. Darth and Bob now share a different key.

Step 6. Every message Alice sends, Darth decrypts with g to the power ax, reads, alters if he wishes, re-encrypts with g to the power bx and passes to Bob. And the reverse.

Notice what Darth did not need: he did not solve a discrete logarithm, did not factor anything, and did not break a cipher. He needed only to sit on the channel and be able to replace two messages.

The attack, performed

# The man in the middle, performed. Both honest parties finish with a key the
# attacker also holds, and neither can tell.

import hashlib

p, g = 353, 3
a, b, x = 97, 233, 41          # Alice's, Bob's, and Darth's private values

print("what SHOULD happen:")
A, B = pow(g, a, p), pow(g, b, p)
print("   Alice sends A = %d, Bob sends B = %d" % (A, B))
print("   Alice computes B^a = %d, Bob computes A^b = %d"
      % (pow(B, a, p), pow(A, b, p)))
print("   one shared secret:", pow(B, a, p))
print()

print("what happens with Darth in the middle. He intercepts BOTH messages")
print("and substitutes his own X = g^x mod p:")
X = pow(g, x, p)
print("   Alice sends A = %d.  Darth keeps it and sends X = %d to Bob." % (A, X))
print("   Bob   sends B = %d.  Darth keeps it and sends X = %d to Alice." % (B, X))
print()
alice_key = pow(X, a, p)
bob_key = pow(X, b, p)
darth_with_alice = pow(A, x, p)
darth_with_bob = pow(B, x, p)
print("   Alice computes X^a mod p        = %d" % alice_key)
print("   Darth  computes A^x mod p        = %d" % darth_with_alice)
print("   the same key:", alice_key == darth_with_alice)
print()
print("   Bob   computes X^b mod p        = %d" % bob_key)
print("   Darth  computes B^x mod p        = %d" % darth_with_bob)
print("   the same key:", bob_key == darth_with_bob)
print()
print("   Alice's key and Bob's key are DIFFERENT:", alice_key != bob_key)
print("   so Darth holds one key with Alice and another with Bob, decrypts")
print("   everything, and re-encrypts it onward. Neither party sees anything")
print("   wrong: the protocol completed, and a key was agreed.")
print()

print("and here is the part that matters. Alice's traffic, read and altered:")
def stream(key, n):
    """A key stream from the agreed secret, standing in for a real cipher."""
    out = b""
    i = 0
    while len(out) < n:
        out += hashlib.sha256(str(key).encode() + i.to_bytes(4, "big")).digest()
        i += 1
    return out[:n]

msg = b"PAY 40000 TO VENDOR 8817"
ct = bytes(m ^ k for m, k in zip(msg, stream(alice_key, len(msg))))
print("   Alice encrypts under her key   :", ct.hex()[:32], "...")
seen = bytes(c ^ k for c, k in zip(ct, stream(darth_with_alice, len(ct))))
print("   Darth decrypts it              :", seen.decode())
altered = b"PAY 40000 TO VENDOR 9999"
onward = bytes(m ^ k for m, k in zip(altered, stream(darth_with_bob, len(altered))))
got = bytes(c ^ k for c, k in zip(onward, stream(bob_key, len(onward))))
print("   Darth re-encrypts his own text :", onward.hex()[:32], "...")
print("   Bob decrypts                   :", got.decode())
print("   Bob received what Darth wrote, not what Alice sent:", got != msg)
print()
print("NOTHING in the exchange detects this, because the exchange never asked")
print("who sent A or B. Diffie-Hellman agrees a key with WHOEVER ANSWERED.")
print()
print("the fix is authentication, and it must be of the exchange itself:")
print("   sign A and B (Station to Station, and IKE's authenticated exchanges);")
print("   or bind the public values to identities with certificates (TLS);")
print("   or derive them from a shared password (a PAKE).")
print("A key agreed without authentication is a key agreed with an unknown party.")
munotes.in250

The Man in the Middle, and Why Diffie-Hellman Needs Authentication

what SHOULD happen:
   Alice sends A = 40, Bob sends B = 248
   Alice computes B^a = 160, Bob computes A^b = 160
   one shared secret: 160

what happens with Darth in the middle. He intercepts BOTH messages
and substitutes his own X = g^x mod p:
   Alice sends A = 40.  Darth keeps it and sends X = 102 to Bob.
   Bob   sends B = 248.  Darth keeps it and sends X = 102 to Alice.

   Alice computes X^a mod p        = 161
   Darth  computes A^x mod p        = 161
   the same key: True

   Bob   computes X^b mod p        = 287
   Darth  computes B^x mod p        = 287
   the same key: True

   Alice's key and Bob's key are DIFFERENT: True
   so Darth holds one key with Alice and another with Bob, decrypts
   everything, and re-encrypts it onward. Neither party sees anything
   wrong: the protocol completed, and a key was agreed.

and here is the part that matters. Alice's traffic, read and altered:
   Alice encrypts under her key   : 241443c03775bc83a06ffdf3c904fd20 ...
   Darth decrypts it              : PAY 40000 TO VENDOR 8817
   Darth re-encrypts his own text : 4764f1d3b9bb8ddcd27ceac80d3be05a ...
   Bob decrypts                   : PAY 40000 TO VENDOR 9999
   Bob received what Darth wrote, not what Alice sent: True

NOTHING in the exchange detects this, because the exchange never asked
who sent A or B. Diffie-Hellman agrees a key with WHOEVER ANSWERED.

the fix is authentication, and it must be of the exchange itself:
   sign A and B (Station to Station, and IKE's authenticated exchanges);
   or bind the public values to identities with certificates (TLS);
   or derive them from a shared password (a PAKE).
A key agreed without authentication is a key agreed with an unknown party.
munotes.in251

The Man in the Middle, and Why Diffie-Hellman Needs Authentication

Read five things out of that run.

Alice's key equals Darth's key with Alice, and Bob's equals Darth's with Bob, and the program checks both. Those two lines are the attack.

Alice's key and Bob's key are different. That is the signature of the attack and, in principle, the way to detect it: if Alice and Bob could compare their keys over an authenticated channel they would see they differ. But they have no authenticated channel; that is why they were doing this.

Neither party sees anything wrong. The protocol ran to completion and produced a key. There is no error, no warning, no anomaly. This is the part students underestimate: the attack is not detected and then tolerated, it is invisible.

And the traffic is altered, not only read. Alice sent PAY 40000 TO VENDOR 8817. Bob received PAY 40000 TO VENDOR 9999. Darth changed the vendor and nobody involved has any way to know.

The fixes are all the same fix. Sign the public values, or bind them to identities with certificates, or derive them from a shared password. Each of those authenticates the exchange, and all three appear in Module 2: Station-to-Station and IKE sign, TLS uses certificates, and password-authenticated key exchange derives.

munotes.in252

The Man in the Middle, and Why Diffie-Hellman Needs Authentication

Why this shapes the rest of the syllabus

A student who understands this chapter can predict the shape of half of Module 2.

Kerberos exists because two parties on a campus network cannot authenticate each other, so a trusted third party vouches for both with tickets.

X.509 and the public-key infrastructure exist to bind a public key to a name with a signature, so that a public value cannot be substituted.

IKE, IPsec's key management, runs Diffie-Hellman and then authenticates it, with signatures or a pre-shared key, and its cookie exchange additionally resists a different attack.

The TLS handshake runs a key exchange and then proves the server's identity with a certificate, and in TLS 1.3 the server signs the whole handshake transcript so that no value in it can have been substituted.

Every one of those four is an answer to this chapter. That is how Module 2 should be read.

Worked example: detecting it after the fact

The situation. Two parties suspect an exchange was intercepted. Can they tell, afterwards?

Step 1: compare the keys. If Alice and Bob can compare their derived secrets over any channel the attacker does not control, they will see them differ. In practice this is done by comparing a short fingerprint of the key, read aloud over a telephone, which is how encrypted voice applications offer verification.

Step 2: compare the public values. Alice knows what A she sent and what value she received as B. Bob knows the same in reverse. If Darth intervened, the value Alice received is not the value Bob sent. Comparing them over an authenticated channel exposes the attack immediately.

Step 3: notice what both steps require. An authenticated channel, however narrow. A telephone call, a meeting, a previously exchanged fingerprint. The attack cannot be detected from inside the exchange, only from outside it.

Step 4: and this is the principle. Authentication cannot be bootstrapped from nothing. Somewhere there must be an initial authentic piece of information: a certificate authority's key that came with the operating system, a password both parties already know, a fingerprint read over the telephone. Every authentication system in this book ends at such a root, and the honest question about any of them is what the root is and who put it there.

Distinctions that carry marks

Passive attackerActive attacker
Canread the channelread, block, alter and inject
Against Diffie-Hellmancannot recover the secretdefeats it completely
Detected bynothing; there is nothing to detectnothing, from inside the protocol
Countermeasurethe exchange itself sufficesauthentication of the exchange
munotes.in253

The Man in the Middle, and Why Diffie-Hellman Needs Authentication

Unauthenticated Diffie-HellmanAuthenticated Diffie-Hellman
Agrees a keyyesyes
With whomwhoever answeredthe party whose identity was verified
Resists a man in the middlenoyes
Examplesthe bare exchange of the previous chapterStation-to-Station, IKEv2, TLS 1.3
The fixHow it authenticatesWhere it appears
Sign the public valueseach party signs g to the power of its secretStation-to-Station, IKE
Certificatesa CA's signature binds the key to a nameTLS, S/MIME, IPsec
A shared passwordonly a party knowing it can complete the exchangepassword-authenticated key exchange
Compare fingerprints out of banda human checks a short hashencrypted voice and messaging applications

What beginners get wrong here

Saying Diffie-Hellman is broken. It is not. It resists a passive eavesdropper completely. It provides no authentication, which is a different statement and the correct one.

Thinking the attacker learns a or b. They learn neither. They do not need to: they run their own exchange with each party.

Saying the parties would notice. They would not. The protocol completes and produces a key. There is no error to see.

Thinking one key is shared by all three. There are two keys: g to the power ax with Alice and g to the power bx with Bob. The attacker translates between them.

Believing authentication can be added later. It must authenticate the exchange itself, because the substitution happens during it. Authenticating the traffic afterwards with the agreed key authenticates the attacker.

Quick revision

  • Diffie-Hellman resists a passive eavesdropper and is defeated completely by an active attacker.
  • The man in the middle intercepts both public values and substitutes his own X. He then shares g to the power ax with Alice and g to the power bx with Bob: two keys, not one.
  • He solves no discrete logarithm, factors nothing, and breaks no cipher. He only replaces two messages.
  • Neither party detects anything, because the exchange never asks who sent a public value. The protocol completes and a key is agreed.
  • Performed in this chapter end to end: Alice sent PAY 40000 TO VENDOR 8817 and Bob received 9999.
  • The rule: a key agreed without authentication is a key agreed with an unknown party.
  • Fixes, all of them authenticating the exchange: sign the public values (Station-to-Station, IKE), certificates (TLS), a shared password (a PAKE), or compare fingerprints out of band.
  • Authentication cannot be bootstrapped from nothing: there is always a root, and the honest question is what it is.

Test yourself

1. Describe the man-in-the-middle attack on Diffie-Hellman. The attacker intercepts Alice's public value and sends Bob his own instead, and intercepts Bob's public value and sends Alice his own. Alice then derives a secret with the attacker, and so does Bob, using a different value. The attacker decrypts each party's traffic with the appropriate key, may alter it, and re-encrypts it under the other key before forwarding. The exchange completes normally and neither party sees any error.

munotes.in254

The Man in the Middle, and Why Diffie-Hellman Needs Authentication

2. What two keys exist after the attack, and why not one? g to the power ax between Alice and the attacker, and g to the power bx between Bob and the attacker, where x is the attacker's secret. They differ because each honest party combined their own secret with the attacker's public value, and the two honest secrets are different. The attacker translates between the two keys.

3. Does the attacker learn either private value? No, and they do not need to. They conduct their own, perfectly ordinary, Diffie-Hellman exchange with each party, so they hold a legitimate shared secret with each and never have to solve a discrete logarithm.

4. Why do the honest parties not detect the attack? Because nothing in the exchange authenticates the origin of the public values. Each party receives a well-formed value, completes the computation, and obtains a key. There is no error condition, no mismatch and no anomaly visible from inside the protocol.

5. Name three ways of preventing the attack, and say what they have in common. Signing the public values, as Station-to-Station and IKE do; binding the public values to identities with certificates, as TLS does; and deriving them from a shared password, as a password-authenticated key exchange does. All three authenticate the exchange itself, so that a substituted public value can be recognised.

6. Can the attack be detected after the fact? Only from outside the exchange. If the two parties compare their derived secrets, or the public values each sent and received, over a channel the attacker does not control, the discrepancy appears at once. That is what comparing a short key fingerprint over a telephone achieves. From inside the protocol there is nothing to detect.

7. What general principle about authentication does this chapter establish? That authentication cannot be bootstrapped from nothing. Every authentication system rests on some initial authentic information, such as a certification authority's key supplied with the operating system, a password both parties already know, or a fingerprint verified in person. The useful question about any such system is what that root is and who is trusted to have placed it there.

Contents This chapter on its own page

munotes.in255

Chapter Forty-Two

Authentication Requirements: The Attacks on a Message

Syllabus topic Module 1, "Message Authentication and Hash Functions: Authentication Requirements"

In one line

Eight things can go wrong with a message in transit, and message authentication answers six of them. Knowing which six, and which two need something else, is the whole of this topic.

In the wording a student can write in an examination: message authentication is the procedure by which communicating parties verify that received messages are authentic, meaning that the contents are unaltered, that the source is who it claims to be, and that the message is timely and in sequence. The requirements are best set out as the list of attacks that authentication must counter: disclosure, traffic analysis, masquerade, content modification, sequence modification, timing modification, source repudiation and destination repudiation.

The eight attacks

1. Disclosure. Releasing message contents to anybody not possessing the appropriate key. A student's marks read off the wire.

2. Traffic analysis. Discovering the pattern of traffic: the frequency and length of messages between parties, from which the nature of the communication may be inferred. Two hundred megabytes from the Examination Section to the printer at three in the morning in November.

3. Masquerade. Insertion of messages into the network from a fraudulent source. This includes the creation of messages by an opponent purporting to come from an authorised entity, and also fraudulent acknowledgements of receipt or non-receipt. A message in the Head of Department's name authorising a mark change.

4. Content modification. Changes to the contents of a message, including insertion, deletion, transposition and modification. An internal mark of 12 altered to 42.

5. Sequence modification. Any modification to a sequence of messages between parties, including insertion, deletion and reordering. The second instalment of a fee receipt delivered before the first, so that the running balance is wrong.

6. Timing modification. Delay or replay of messages. In a connection-oriented application an entire session or sequence of messages could be a replay of a previous valid sequence; or individual messages could be delayed or replayed. A fee payment authorisation held back for an hour and then sent four times.

7. Source repudiation. Denial of transmission of a message by the source. A department denying that it submitted the marks it submitted.

8. Destination repudiation. Denial of receipt of a message by the destination. The Examination Section denying that it received the marks the department sent.

Which service answers which, and the two that message authentication does not

AttackAnswered byNotes
1. Disclosuremessage encryption, not authenticationconfidentiality is a different service
2. Traffic analysistraffic flow confidentiality: padding and routing controlnot answered by authentication at all
3. Masquerademessage authenticationthe authenticator can only be made by a key holder
4. Content modificationmessage authenticationthe authenticator covers the contents
5. Sequence modificationmessage authentication with a sequence numberthe authenticator must cover the number
6. Timing modificationmessage authentication with a timestamp or noncethe authenticator must cover the time
7. Source repudiationdigital signature, not a MACneeds asymmetry; see the services chapter
8. Destination repudiationdigital signature plus a protocolthe recipient must be made to produce a signed receipt
munotes.in256

Authentication Requirements: The Attacks on a Message

Read the table's shape. Items 1 and 2 are confidentiality problems, not authentication problems, and a question that asks "what does message authentication protect against" should exclude them and say why. Items 3 to 6 are what message authentication is for. Items 7 and 8 need a digital signature, because a shared-key authenticator can be produced by either party.

And notice items 5 and 6 carefully, because they are the ones students get wrong. A message authentication code over the contents alone does not stop reordering or replay: the replayed message is genuine and its tag is valid. The sequence number or the timestamp must be inside the data the authenticator covers, and that is a design requirement, not an optional extra.

Worked example: designing against all eight

The requirement. A department submits internal marks to the University, one message per student, over the campus network.

Attack 4, content modification. Compute a message authentication code over the whole message with a key shared with the University. An altered message fails the check.

Attack 3, masquerade. The same MAC answers it: only a holder of the key can produce a valid tag, so a message from a fraudulent source has no valid tag.

Attack 5, sequence modification. Number the messages 1 to N and include the number inside the data the MAC covers. A reordered or deleted message is now detectable, because the receiver expects number 7 and the tag on the message claiming to be 7 is a tag over 9.

Attack 6, timing modification. Include a timestamp, also inside the MAC's coverage, and reject a message whose timestamp is outside a window. Or use a nonce and remember the nonces seen in this session.

Attack 1, disclosure. Encrypt the message. The MAC does not hide anything, so this is a second, separate mechanism.

Attack 2, traffic analysis. Send a fixed number of messages of fixed length every day, padding with dummies. This is expensive, and most systems accept the exposure. Saying so is more honest than pretending padding is free.

Attacks 7 and 8, repudiation. Replace the MAC with a digital signature over each message, so the University can prove to a third party which department signed it. And require the University to return a signed receipt, so the department can prove delivery. Neither is achievable with the shared-key MAC, however strong.

munotes.in257

Authentication Requirements: The Attacks on a Message

The step that carries the marks. Naming, for each attack, whether the answer is authentication, confidentiality, a sequence number, a timestamp, or a signature. A design that applies one MAC and claims to have answered all eight has answered four.

What beginners get wrong here

Thinking message authentication provides confidentiality. It does not. A MAC is appended to a message that is still readable.

Thinking a MAC stops replay. It does not, unless a sequence number or timestamp is inside its coverage. The replayed message is genuine and its tag is valid.

Putting the sequence number outside the authenticator. Then the attacker changes the number and the tag still verifies over the contents. Whatever must not be altered must be inside the coverage.

Claiming a MAC gives non-repudiation. It gives authentication between two parties who share a key, and nothing that can be shown to a third.

Omitting traffic analysis because it is inconvenient. It is one of the eight and the honest answer is that it costs padding and most systems decline to pay.

Quick revision

  • Eight attacks: disclosure, traffic analysis, masquerade, content modification, sequence modification, timing modification, source repudiation, destination repudiation.
  • 1 and 2 are confidentiality problems, answered by encryption and by traffic padding, not by authentication.
  • 3 to 6 are what message authentication answers.
  • 7 and 8 need a digital signature, because a shared key lets either party produce the authenticator.
  • A MAC over the contents alone does not stop reordering or replay. The sequence number and the timestamp must be inside the data the authenticator covers.
  • Authentication means: contents unaltered, source as claimed, and timely and in sequence.

Test yourself

1. List the eight attacks that message authentication must be considered against. Disclosure; traffic analysis; masquerade; content modification; sequence modification; timing modification; source repudiation; and destination repudiation.

2. Which two are not answered by message authentication, and what answers them? Disclosure and traffic analysis. Disclosure is answered by encryption, that is by the data confidentiality service. Traffic analysis is answered by traffic flow confidentiality, which requires traffic padding and routing control, because encryption leaves the pattern of messages visible.

3. Which two require a digital signature rather than a message authentication code? Source repudiation and destination repudiation. A message authentication code is computed with a key both parties hold, so either could have produced it and neither can prove the other's authorship to a third party. A digital signature made with a private key can be produced by only one party and verified by anybody.

4. Why does a message authentication code over the contents not prevent replay? Because a replayed message is a genuine message: its contents are unaltered and its tag is a valid tag over those contents. Nothing in the message distinguishes a first delivery from a second. Freshness must be supplied separately, by including a sequence number, a timestamp or a nonce in the data the authenticator covers.

munotes.in258

Authentication Requirements: The Attacks on a Message

5. Why must a sequence number be inside the authenticator's coverage? Because if it is outside, an attacker can alter the number while leaving the contents and the tag unchanged, and the tag will still verify. Only data covered by the authenticator is protected, so anything that must not be altered, including sequence numbers and timestamps, must be part of what is authenticated.

6. Define message authentication. The procedure by which communicating parties verify that received messages are authentic: that the contents have not been altered, that the source is who it claims to be, and that the message is timely and in the correct sequence.

7. Give a college example of sequence modification and of timing modification. Sequence modification: the second instalment of a fee receipt is delivered before the first, so the running balance shown to the student is wrong. Timing modification: a fee payment authorisation is held back for an hour and then delivered four times, so the student is charged four times for one payment.

Contents This chapter on its own page

munotes.in259

Chapter Forty-Three

Authentication Functions: Encryption, MAC and Hash

Syllabus topic Module 1, "Message Authentication and Hash Functions: Authentication Functions"

In one line

There are three ways to produce an authenticator: encrypt the message, compute a keyed tag, or compute an unkeyed digest and protect it. Each gives a different set of services, and only the second and third are called message authentication.

In the wording a student can write in an examination: an authenticator is a value to be used to authenticate a message. The functions that produce one fall into three classes: message encryption, in which the ciphertext of the entire message serves as its authenticator; a message authentication code, a fixed-length value computed from the message and a secret key; and a hash function, a function mapping a message of any length to a fixed-length hash value, which serves as the authenticator once it is itself protected.

Class 1: message encryption

The idea is that if the message decrypts to something sensible, it must have been encrypted by somebody holding the key, so it is authentic. The idea is nearly right and the gap is the whole topic.

With a symmetric key

Asha encrypts the message with the key she shares with Bharat. Bharat decrypts it. Nobody else can have produced a ciphertext that decrypts sensibly, so the message is confidential and apparently authenticated.

The gap: how does Bharat know the decryption is correct? If the plaintext is an arbitrary binary file, then every ciphertext decrypts to some binary file, and Bharat has no way to tell the one Asha sent from one an attacker made up. Authentication requires that Bharat be able to recognise a valid plaintext.

Two answers, and the difference between them matters.

Internal error control. Compute a checksum or frame check sequence over the plaintext, append it to the plaintext, and encrypt the whole thing. The receiver decrypts and checks. This works, because an attacker who alters the ciphertext produces a plaintext whose checksum will not match.

External error control. Compute the checksum over the ciphertext and append it outside the encryption. This does not authenticate, because an attacker can construct any ciphertext they like and compute a correct checksum over it. The receiver's check passes and the plaintext is rubbish, or worse, is something the attacker chose.

So the order matters, and it is examinable: the error-control value must be computed on the plaintext and encrypted with it. That is the same lesson as the CBC bit-flipping chapter in a different form.

With a public key

Asha encrypts with Bharat's public key. This gives confidentiality only, and no authentication at all, because Bharat's public key is public and anybody could have sent it.

For authentication, Asha encrypts with her own private key, which anybody can undo with her public key. That gives authentication and a signature and no confidentiality.

munotes.in260

Authentication Functions: Encryption, MAC and Hash

For both, Asha signs with her private key and then encrypts with Bharat's public key. Four public-key operations for one message, which is why the hash-based arrangement of the next section is used instead.

Class 2: a message authentication code

A fixed-length tag computed from the message and a shared secret key, appended to the message and checked by recomputation. The next chapter is about it.

What it gives. Authentication, and integrity, and nothing else. The message is not encrypted, which is often exactly what is wanted: a public announcement that must be provably from its source.

Three arrangements.

Message plus tag, unencrypted. Authentication only.

Tag computed on the plaintext, then the whole thing encrypted. Authentication and confidentiality, with the authentication tied to the plaintext.

Message encrypted, then a tag computed on the ciphertext. Authentication and confidentiality, with the authentication tied to the ciphertext. This is the arrangement modern protocols prefer, because the receiver can check the tag before decrypting and so need never process an attacker's ciphertext at all. It is called encrypt-then-MAC.

Class 3: a hash function

A hash takes a message of any length and produces a fixed-length digest, with no key. So a bare digest authenticates nothing: an attacker who alters the message recomputes the digest.

The digest must therefore be protected, and there are four ways.

  1. Encrypt the message and the digest together with a shared key. Confidentiality and authentication.
  2. Encrypt only the digest with a shared key. Authentication, no confidentiality, and much cheaper than encrypting the whole message.
  3. Encrypt only the digest with the sender's private key. Authentication and a digital signature. This is what every signature scheme in Module 2 does.
  4. Encrypt only the digest with the sender's private key, then encrypt the lot with a shared key. All three services.

And the fifth, which needs no encryption at all: append a shared secret to the message, hash the result, and send the message with that digest. Nobody without the secret can compute the digest. That is the idea HMAC makes safe, and the HMAC chapter shows why the naive version of it is forgeable.

Worked example: choosing an arrangement

Case 1: a public examination timetable on a college website. Everybody may read it and nobody may alter it. Hash the timetable and sign the digest with the college's private key. No encryption at all, because there is nothing to hide. Anybody can verify.

Case 2: a marks file between two offices that share a key. Confidential and authenticated, and no third party need be convinced. Encrypt with AES, then compute a MAC over the ciphertext. Encrypt-then-MAC, so a forged ciphertext is rejected before decryption.

munotes.in261

Authentication Functions: Encryption, MAC and Hash

Case 3: a payment instruction that the bank must be able to prove came from the college. Non-repudiation is required, so a MAC will not do. Hash, sign the digest with the college's private key, and encrypt the result with the bank's public key. Two public-key operations on short values and one symmetric encryption of the message.

Case 4: a broadcast to two thousand students. A signature, because a shared key would have to be shared with all two thousand, and then any of them could forge a broadcast.

The step that carries the marks. In each case, naming which services are needed first and then choosing the arrangement, rather than choosing a mechanism and describing what it happens to give.

Distinctions that carry marks

ArrangementConfidentialityAuthenticationSignature
Symmetric encryption of the message, with internal error controlyesyesno
Symmetric encryption, with external error controlyesnono
Public-key encryption with the receiver's public keyyesnono
Encryption with the sender's private keynoyesyes
Sign, then encrypt to the receiveryesyesyes
Message plus MAC, unencryptednoyesno
MAC on the plaintext, then encryptyesyesno
Encrypt, then MAC on the ciphertextyesyesno
Hash, then encrypt both with a shared keyyesyesno
Hash, then encrypt the digest with a shared keynoyesno
Hash, then encrypt the digest with the private keynoyesyes
Message plus hash of (message plus a shared secret)noyesno
Internal error controlExternal error control
The check value is computed onthe plaintextthe ciphertext
Thenencrypted with the messageappended outside
Authenticatesyesno
Becausean altered ciphertext gives a plaintext whose check failsan attacker can compute a correct check over any ciphertext

What beginners get wrong here

Saying encryption authenticates. It authenticates only if the receiver can recognise a valid plaintext, which needs internal error control or naturally structured data.

Confusing internal and external error control. Internal works; external does not. The check value goes inside the encryption.

Saying public-key encryption authenticates. Encrypting with the receiver's public key authenticates nothing, because the key is public.

Encrypting the whole message to authenticate it when a digest would do. Hashing and protecting the digest is far cheaper and is what every real protocol does.

Thinking a bare hash authenticates. It has no key. The digest must be encrypted, signed, or computed over the message plus a secret.

Quick revision

  • Three classes of authentication function: message encryption, a message authentication code, and a hash function whose digest is then protected.
  • Encryption authenticates only if the receiver can recognise a valid plaintext. Hence internal error control (check value on the plaintext, encrypted with it) works, and external error control (check value on the ciphertext) does not.
  • Public-key encryption with the receiver's public key gives confidentiality and no authentication. With the sender's private key it gives authentication and a signature and no confidentiality.
  • A MAC gives authentication and integrity and no confidentiality. Encrypt-then-MAC is preferred, because the tag can be checked before decrypting.
  • A bare hash authenticates nothing; the digest must be encrypted with a shared key, signed with a private key, or computed over the message plus a shared secret.
  • Hash then sign the digest is what every signature scheme does, because it signs a short fixed-length value instead of the message.
munotes.in262

Authentication Functions: Encryption, MAC and Hash

Test yourself

1. Name the three classes of authentication function. Message encryption, where the ciphertext of the whole message is the authenticator; a message authentication code, a fixed-length value computed from the message and a secret key; and a hash function, whose fixed-length digest becomes an authenticator once it is itself protected.

2. Why does symmetric encryption not by itself authenticate a message? Because the receiver must be able to tell a correct decryption from an incorrect one. If the plaintext is arbitrary binary data then every ciphertext decrypts to some plausible-looking plaintext, and the receiver cannot distinguish the sender's message from an attacker's invention. The plaintext must therefore carry recognisable structure, which is what a check value supplies.

3. Distinguish internal from external error control and say which authenticates. Internal error control computes a check value over the plaintext, appends it to the plaintext, and encrypts both; the receiver decrypts and verifies, so any alteration of the ciphertext produces a plaintext whose check value fails. External error control computes the check value over the ciphertext and appends it outside the encryption; an attacker can fabricate any ciphertext and compute a correct check value over it, so the check passes and nothing is authenticated. Only internal error control authenticates.

4. What does encrypting with the receiver's public key provide, and what does encrypting with the sender's private key provide? Encrypting with the receiver's public key provides confidentiality only, because anybody can use a public key, so the message proves nothing about its origin. Encrypting with the sender's private key provides authentication and a digital signature, because only the sender could have produced it, but no confidentiality, since anybody can undo it with the public key.

5. What is encrypt-then-MAC and why is it preferred? The message is encrypted and the message authentication code is then computed over the ciphertext. It is preferred because the receiver can verify the tag before decrypting, so a forged or altered ciphertext is rejected without ever being processed by the decryption routine, which removes a whole class of attacks that exploit the decryption of attacker-chosen data.

munotes.in263

Authentication Functions: Encryption, MAC and Hash

6. Why does a bare hash value not authenticate a message? Because a hash function takes no key, so an attacker who alters the message simply recomputes the digest and sends the pair. The digest must be protected: encrypted with a shared key, encrypted with the sender's private key, or computed over the message concatenated with a shared secret.

7. Give the arrangement that provides confidentiality, authentication and a digital signature, and say why the digest is used. Hash the message, encrypt the digest with the sender's private key to form the signature, then encrypt the message and signature together with a key shared with the receiver, or with the receiver's public key. The digest is used because public-key operations are expensive and limited in size, so signing a short fixed-length value rather than the whole message makes the scheme practical.

Contents This chapter on its own page

munotes.in264

Chapter Forty-Four

Message Authentication Codes

Syllabus topic Module 1, "Message Authentication and Hash Functions: Message Authentication Codes"

In one line

A short tag computed from the message and a shared secret key, which anybody holding the key can recompute and check. It proves the message came from a key holder and was not altered, and it proves nothing to anybody else.

In the wording a student can write in an examination: a message authentication code, or MAC, also called a cryptographic checksum, is a small fixed-size block of data generated from a message and a secret key, MAC = C(K, M). The sender appends it to the message; the receiver recomputes it with the same key and compares. If they match, the receiver is assured that the message has not been altered, that it is from the alleged sender, and, if the message includes a sequence number, that the sequence is correct.

What a MAC must satisfy

Three requirements, and the third is the one an examination asks about.

1. Given a message and its MAC, it must be infeasible to construct a different message with the same MAC. This is the forgery requirement.

2. The MAC values must be uniformly distributed, so that for two randomly chosen messages the chance of equal MACs is 2 to the power minus n, where n is the number of bits in the MAC.

3. The MAC must depend equally on all bits of the message. If some bits influence the tag less than others, an attacker who knows the message can change those bits with a better than random chance of the tag still matching.

Requirement 3 is the one that distinguishes a MAC from a simple checksum. A cyclic redundancy check satisfies 2 and fails 1 and 3 completely, and that is why a CRC is not a MAC however long it is.

The three things a MAC is not

# A message authentication code, and the forgery that plain CBC-MAC allows.

import hashlib, hmac as _hmac

KEY = b"a shared secret key"

def mac(key, message):
    """A MAC built on a hash, which is what HMAC does properly."""
    return _hmac.new(key, message, hashlib.sha256).digest()[:8]

msg = b"PAY 0500 TO VENDOR 8817"
tag = mac(KEY, msg)
print("message :", msg.decode())
print("tag     :", tag.hex(), "(8 bytes, truncated from SHA-256)")
print()
print("the receiver recomputes and compares:")
print("   recomputed:", mac(KEY, msg).hex(), " matches:", mac(KEY, msg) == tag)
print()
print("alter one character and the tag no longer matches:")
bad = b"PAY 0500 TO VENDOR 9999"
print("   altered   :", bad.decode())
print("   its tag   :", mac(KEY, bad).hex())
print("   matches the original tag:", mac(KEY, bad) == tag)
print()

print("a MAC is NOT reversible. Many messages share one tag, by construction:")
print("   tag is 8 bytes, so 2 to the power 64 possible tags")
print("   messages of 23 bytes: 2 to the power 184 of them")
print("   so on average 2 to the power 120 messages share each tag.")
print("   The tag does not contain the message and cannot be inverted.")
print()

print("and why a MAC is not a signature. Both parties hold KEY, so BHARAT")
print("could have produced this tag himself:")
print("   Bharat computes the same tag :", mac(KEY, msg).hex())
print("   identical to Asha's          :", mac(KEY, msg) == tag)
print("   A third party cannot tell which of them made it. No non-repudiation.")
print()

print("PLAIN CBC-MAC, and the forgery it allows on variable-length messages.")
print("Built on a small block cipher so the blocks are readable:")

def block_cipher(block, key):
    x = int.from_bytes(block, "big")
    for r in range(4):
        x = (x * 0x2545 + key + r) & 0xFFFFFFFF
        x = ((x << 9) | (x >> 23)) & 0xFFFFFFFF
        x ^= (key >> (8 * r)) & 0xFFFFFFFF
    return x.to_bytes(4, "big")

def xor(a, b):
    return bytes(p ^ q for p, q in zip(a, b))

def cbc_mac(message, key):
    prev = bytes(4)
    for i in range(0, len(message), 4):
        prev = block_cipher(xor(prev, message[i:i + 4]), key)
    return prev

K = 0x2B7E1516
one = b"PAY1"
t1 = cbc_mac(one, K)
print("   MAC of %r            = %s" % (one.decode(), t1.hex()))
two = one + xor(t1, b"PAY1")
t2 = cbc_mac(two, K)
print("   MAC of the forged two-block message = %s" % t2.hex())
print("   which equals the one-block MAC      :", t1 == t2)
print("   The attacker built a LONGER message with the SAME tag, knowing only")
print("   one message and its tag. No key was needed.")
print()
print("   The fix is to include the LENGTH, or to use a different final key:")
print("   CMAC (SP 800-38B) and HMAC (FIPS 198-1) both do this correctly.")
munotes.in265

Message Authentication Codes

message : PAY 0500 TO VENDOR 8817
tag     : 33308090dac16738 (8 bytes, truncated from SHA-256)

the receiver recomputes and compares:
   recomputed: 33308090dac16738  matches: True

alter one character and the tag no longer matches:
   altered   : PAY 0500 TO VENDOR 9999
   its tag   : 8b0a5a9075cfdeb0
   matches the original tag: False

a MAC is NOT reversible. Many messages share one tag, by construction:
   tag is 8 bytes, so 2 to the power 64 possible tags
   messages of 23 bytes: 2 to the power 184 of them
   so on average 2 to the power 120 messages share each tag.
   The tag does not contain the message and cannot be inverted.

and why a MAC is not a signature. Both parties hold KEY, so BHARAT
could have produced this tag himself:
   Bharat computes the same tag : 33308090dac16738
   identical to Asha's          : True
   A third party cannot tell which of them made it. No non-repudiation.

PLAIN CBC-MAC, and the forgery it allows on variable-length messages.
Built on a small block cipher so the blocks are readable:
   MAC of 'PAY1'            = 881fa296
   MAC of the forged two-block message = 881fa296
   which equals the one-block MAC      : True
   The attacker built a LONGER message with the SAME tag, knowing only
   one message and its tag. No key was needed.

   The fix is to include the LENGTH, or to use a different final key:
   CMAC (SP 800-38B) and HMAC (FIPS 198-1) both do this correctly.
munotes.in266

Message Authentication Codes

Read four things out of that run.

It works, and an alteration is caught. One character changed and the tag is entirely different, because the underlying hash has the avalanche property.

A MAC is not reversible, and the counting shows why. The tag is 8 bytes, so there are 2 to the power 64 possible tags; a 23-byte message has 2 to the power 184 possibilities; so on average 2 to the power 120 messages share each tag. The tag does not contain the message. Many students describe a MAC as "encryption that cannot be decrypted", which is the wrong picture: it is a many-to-one function, not an encryption at all.

A MAC is not a signature, and the run demonstrates it. Bharat computes the identical tag, because he holds the same key. A third party shown the message and the tag cannot say which of the two produced it. This is the services chapter's argument, made concrete.

And plain CBC-MAC is forgeable on variable-length messages. Given one message PAY1 and its tag, the attacker constructs a two-block message whose tag is the same, by appending the tag exclusive-ored with the first block. No key was used. That is why plain CBC-MAC must never be used on messages of varying length, and why SP 800-38B's CMAC applies a different final key and HMAC uses a nested hash.

Why the forgery works, in one paragraph

CBC-MAC computes T = E(K, M1) for a one-block message. Now consider the two-block message whose first block is M1 and whose second block is T XOR M1. The MAC of that is E(K, (T XOR M1) XOR E(K, M1)), which is E(K, (T XOR M1) XOR T), which is E(K, M1), which is T. So the longer message has the same tag, and the attacker needed only M1 and T. The fix is to make the computation depend on the length, which CMAC does by using a different key for the final block.

The alternatives, and which to use

Plain CBC-MACCMACHMAC
Built ona block ciphera block ciphera hash function
Specified inolder standardsSP 800-38BFIPS 198-1, RFC 2104
Safe for variable-length messagesnoyesyes
How it fixes the length problemit does nota different key for the final blockthe nested construction
Speeda cipher passa cipher passa hash pass, usually faster in software
Use itneverwhere a block cipher is already presentthe default choice
munotes.in267

Message Authentication Codes

Worked example: where a MAC is the right answer and where it is not

Case 1: two college offices exchanging marks, sharing a key. A MAC is exactly right. Both hold the key, neither needs to convince a third party, and a MAC is far cheaper than a signature.

Case 2: a college publishing a timetable for two thousand students. A MAC is wrong. The key would have to be given to all two thousand, and then any one of them could forge a timetable. A signature is the answer.

Case 3: a bank needing to prove which college authorised a payment. A MAC is wrong, because the bank holds the key too and could have produced the tag. A signature is the answer.

Case 4: a protocol authenticating each packet of a live connection, thousands per second. A MAC is right. The two endpoints already share a session key from the handshake, no third party is involved, and a signature per packet would be far too slow.

The step that carries the marks. The test is always the same: is there a third party who must be convinced, and is the key held by more than the parties whose word is at stake? If either answer is yes, a MAC will not do.

Distinctions that carry marks

MACDigital signature
Keyone, shareda private key to make, a public key to check
Who can produce itanybody holding the keyonly the private key holder
Who can verify itanybody holding the keyanybody at all
Non-repudiationnoyes
Speedfastslow, so it signs a digest
Length8 to 32 bytes typicallyas long as the modulus
MACHash
Keyyesno
Authenticates on its ownyesno
Same input, same outputyes, given the keyyes
Reversibleno, and many messages share a tagno
MACEncryption
Output lengthfixed and shortas long as the message
Reversiblenoyes
Provides confidentialitynoyes
Provides integrityyesnot by itself

What beginners get wrong here

Calling a MAC "encryption that is not decrypted". It is a many-to-one function. Many messages share a tag, by construction, and the tag does not contain the message.

Claiming a MAC gives non-repudiation. It does not, and the run shows Bharat producing Asha's tag.

Using a CRC as a MAC. A CRC has no key and is linear, so an attacker alters the message and fixes up the CRC. Length is irrelevant.

munotes.in268

Message Authentication Codes

Using plain CBC-MAC on variable-length messages. The forgery is in this chapter. Use CMAC or HMAC.

Comparing tags with an ordinary equality test. A comparison that stops at the first difference leaks how many bytes matched, and the timing recovers the tag. Use a constant-time comparison.

Quick revision

  • MAC = C(K, M): a short fixed-length tag from the message and a shared key, appended and checked by recomputation.
  • Three requirements: infeasible to find another message with the same tag; uniformly distributed tags, so a chance collision is 2 to the power minus n; and dependence on all bits equally.
  • A MAC is not reversible: with an 8-byte tag and a 23-byte message about 2 to the power 120 messages share each tag.
  • A MAC is not a signature: the verifier holds the key and could have produced the tag, so there is no non-repudiation.
  • Plain CBC-MAC is forgeable on variable-length messages: from M1 and its tag T, the two-block message M1 || (T XOR M1) has the same tag. Performed in this chapter.
  • Use CMAC (SP 800-38B) where a block cipher is present, or HMAC (FIPS 198-1) as the default.
  • A CRC is not a MAC: no key, and linear.
  • Compare tags in constant time.

Test yourself

1. Define a message authentication code and state the three requirements on it. A fixed-length value computed from a message and a secret key, MAC = C(K, M), appended to the message and verified by recomputation. It must be infeasible, given a message and its MAC, to construct a different message with the same MAC; the MAC values must be uniformly distributed, so that two random messages collide with probability 2 to the power minus n; and the MAC must depend equally on all bits of the message.

2. Why is a MAC not reversible, and why is that not a defect? Because it maps messages of any length to a short fixed-length tag, so it is many-to-one: with an 8-byte tag and 23-byte messages, about 2 to the power 120 messages share each tag. It is not a defect because the purpose is verification rather than recovery: the receiver already has the message and needs only to confirm it.

3. Why does a MAC not provide non-repudiation? Because the key is shared, so the verifier can compute any tag the sender can. Presented with a message and a valid tag, a third party cannot determine which of the two key holders produced it, so neither party's authorship can be proved against their denial.

4. Show how a forgery is possible against plain CBC-MAC. Let M1 be a one-block message with tag T, so T = E(K, M1). Consider the two-block message whose blocks are M1 and T XOR M1. Its CBC-MAC is E(K, (T XOR M1) XOR E(K, M1)), which is E(K, (T XOR M1) XOR T), which is E(K, M1), which is T. The attacker therefore produces a longer message with the same tag knowing only M1 and T, and no key.

munotes.in269

Message Authentication Codes

5. Why is a cyclic redundancy check not a message authentication code? Because it has no key, so anybody can compute it, and because it is linear, so an attacker who alters the message can adjust it to make the check pass. Both failures are independent of its length.

6. Name two correct constructions and say what each is built on. CMAC, specified in NIST SP 800-38B, built on a block cipher and using a separately derived key for the final block so that the computation depends on the message length. HMAC, specified in FIPS 198-1 and RFC 2104, built on a hash function using a nested construction with two derived keys.

7. A college wants to publish a timetable that two thousand students can verify. Is a MAC suitable? No. Verification with a MAC requires the key, so every student would need it, and any of them could then forge a timetable. The correct mechanism is a digital signature: the college signs with its private key and every student verifies with the public one, which they can do without being able to produce a signature themselves.

Contents This chapter on its own page

munotes.in270

Chapter Forty-Five

Hash Functions: What One Must Do

Syllabus topic Module 1, "Message Authentication and Hash Functions: Hash Functions"

In one line

A hash function squeezes a message of any length into a short fixed-length digest, and three separate things must be hard: finding a message for a given digest, finding a second message matching a given one, and finding any two messages that match.

In the wording a student can write in an examination: a hash function H accepts a variable-length block of data M as input and produces a fixed-size hash value, or message digest, h = H(M). A cryptographic hash function must additionally satisfy: preimage resistance, that for a given digest it is computationally infeasible to find a message hashing to it; second preimage resistance, also called weak collision resistance, that for a given message it is infeasible to find a different message with the same digest; and collision resistance, also called strong collision resistance, that it is infeasible to find any pair of distinct messages with the same digest.

The three properties, and why they are three

They look similar and they are not, and the difference is entirely in what the attacker is given.

Preimage resistance. The attacker is given a digest h and must find any M with H(M) = h. The cost for an ideal n-bit hash is 2 to the power n.

Second preimage resistance. The attacker is given a message M and must find a different M' with H(M') = H(M). The cost for an ideal n-bit hash is also 2 to the power n.

Collision resistance. The attacker is given nothing and must find any two distinct messages with the same digest. The cost for an ideal n-bit hash is only 2 to the power n/2, by the birthday bound of the chapter after next.

So collision resistance is much weaker than the other two, for the same digest size, and that single fact explains the whole history of hash functions: MD5's 128 bits gave 64 bits of collision resistance, SHA-1's 160 gave 80, and both fell to collisions long before anybody came near a preimage.

And the implication for use. A scheme whose security needs only preimage resistance can survive a broken hash for years. A scheme that needs collision resistance cannot. Digital signatures need collision resistance, because the birthday attack of the later chapter produces two documents with one digest and one signature covers both. HMAC does not, which is why SHA-1 inside HMAC is still acceptable while SHA-1 in a signature is not.

The other requirements

A complete answer gives five more, which are engineering rather than cryptography.

Any size of input. H applies to a block of data of any size.

Fixed-length output. H produces a fixed-length output, whatever the input size.

munotes.in271

Hash Functions: What One Must Do

Easy to compute. H(x) is relatively easy to compute for any given x, so that the scheme is practical in hardware and software.

Deterministic. The same input always gives the same digest. This is obvious and is worth stating, because it is why a hash cannot be used as an encryption: there is no key and nothing is hidden from somebody who can guess the input.

The avalanche property. A one-bit change in the input changes about half the bits of the output. Not a formal requirement but a consequence of the other three, and the thing a reader can measure.

What a hash is used for, and which property each use needs

UseNeedsWhy
Message authentication with a MAC or signaturecollision resistancethe birthday attack produces two messages one signature covers
Password storagepreimage resistance, plus slownessthe attacker has the digest and wants the password
File integrity checkingsecond preimage resistancethe attacker has the file and wants a different one with the same digest
Digital signaturescollision resistancethe signature is over the digest
Commitment: publish a digest now, reveal the value laterpreimage and collision resistancethe value must not be guessable, and you must not be able to change your mind
Deduplication in storagecollision resistancetwo different files with one digest would be stored as one
Proof of workpreimage resistancefinding an input whose digest has a pattern
A hash table in ordinary programmingnone of themspeed only, and a cryptographic hash is the wrong tool

The last row is worth stating in an answer: a non-cryptographic hash for a hash table is not a weak cryptographic hash, it is a different thing for a different purpose, and using SHA-256 to index a dictionary is as much a mistake as the reverse.

And password storage needs slowness, which is not on the list of hash requirements at all: a fast hash is a feature for authentication and a defect for passwords, because the attacker's guessing is as fast as your checking. That is why password hashing uses a deliberately slow, salted, parameterised construction rather than a bare hash.

Worked example: which property has failed, and does it matter

The situation. A collision is published for a hash function H: two different files with the same digest. Four systems use H. Which are broken?

System 1: signatures on college certificates. Broken. A forger prepares a genuine certificate and a fraudulent one with the same digest, has the genuine one signed, and attaches the signature to the fraudulent one. Collision resistance is exactly what this needs.

System 2: passwords stored as H(password). Not broken by this. The attacker holding a digest still needs a preimage, and a collision gives them nothing: they need the password for that digest, not any pair of colliding strings. (The system has other problems: no salt and a fast hash.)

munotes.in272

Hash Functions: What One Must Do

System 3: HMAC over H for authenticating packets. Not broken. HMAC's security proof does not rest on collision resistance of the underlying hash. This is why RFC 6194 could restrict SHA-1 while HMAC-SHA-1 remained acceptable.

System 4: file integrity, comparing a downloaded file's digest against a published one. Broken, but the attack is harder than it sounds. The attacker needs a second preimage for the specific published file, which a collision does not give. However, if the attacker can influence the original file, they can prepare a colliding pair and substitute afterwards, so the answer depends on who created the file.

The step that carries the marks. Naming the property each system depends on, and noticing that a collision is not a universal break. An answer that says "the hash is broken so everything using it is broken" has not understood the three properties.

Distinctions that carry marks

Preimage resistanceSecond preimage resistanceCollision resistance
The attacker is givena digesta messagenothing
Must findany message with that digesta different message with the same digestany colliding pair
Cost for an ideal n-bit hash2 to the power n2 to the power n2 to the power n/2
Also calledone-wayweak collision resistancestrong collision resistance
Needed bypassword storage, proof of workfile integritysignatures, and any MAC on the digest
HashMACEncryption
Keynoyesyes
Output lengthfixedfixedas long as the input
Reversiblenonoyes
Authenticates alonenoyesnot by itself

What beginners get wrong here

Merging the three resistances. They differ in what the attacker is given, and the collision cost is the square root of the other two.

Saying a broken hash breaks everything using it. It depends which property failed and which property the system needs.

Saying a hash is "one-way encryption". There is no key, nothing is recoverable, and nothing is hidden from somebody who can guess the input. It is not encryption.

Using a bare fast hash for passwords. A fast hash helps the attacker as much as the defender. Passwords need salt and deliberate slowness.

Using a cryptographic hash for a hash table. Different purpose, much slower, and no benefit.

Quick revision

  • A hash takes any length to a fixed length, with no key, and is deterministic.
  • Preimage: given a digest, find a message. Cost 2 to the power n.
  • Second preimage (weak collision resistance): given a message, find a different one with the same digest. Cost 2 to the power n.
  • Collision (strong collision resistance): find any pair. Cost 2 to the power n/2, by the birthday bound.
  • Collision resistance is the weak one, and it is why MD5 and SHA-1 fell to collisions long before preimages.
  • Signatures need collision resistance. HMAC does not. That is why SHA-1 in a signature is unacceptable and SHA-1 inside HMAC is not.
  • Other requirements: any input size, fixed output, easy to compute, deterministic; plus the avalanche property as a consequence.
  • Uses: message authentication, password storage (needs slowness, not on the list), file integrity, signatures, commitment, deduplication, proof of work.
munotes.in273

Hash Functions: What One Must Do

Test yourself

1. Define a cryptographic hash function and its three resistance properties. A function taking a variable-length input to a fixed-length digest, with no key. Preimage resistance: given a digest, it is infeasible to find any message hashing to it. Second preimage resistance: given a message, it is infeasible to find a different message with the same digest. Collision resistance: it is infeasible to find any two distinct messages with the same digest.

2. Why is collision resistance weaker than the other two for the same digest size? Because the attacker chooses both messages and is not tied to any given value, so the birthday bound applies: among about 2 to the power n/2 randomly chosen messages a matching pair becomes likely. Preimage and second preimage resistance both require hitting a specific target and cost 2 to the power n.

3. Why is SHA-1 unacceptable in a digital signature but acceptable inside HMAC? Because a signature is computed over the digest, so a collision produces two documents that one signature covers, and collision resistance is therefore essential; SHA-1's collision resistance has been broken. HMAC's security does not rest on the collision resistance of its hash, so a collision does not give an attacker a forgery, and HMAC-SHA-1 remains acceptable.

4. State the other requirements on a hash function besides the three resistances. It must apply to a block of data of any size; it must produce a fixed-length output; it must be relatively easy to compute for any input, so that implementations are practical; and it must be deterministic. As a consequence of the resistances it will also exhibit the avalanche property, that a one-bit input change alters about half the output bits.

5. Which property does password storage depend on, and what does it need beyond the three? Preimage resistance, because the attacker holds the digest and wants a password that produces it. Beyond the three it needs deliberate slowness and a per-password salt, because a fast hash lets an attacker test guesses as quickly as the system verifies them, and a salt prevents one precomputed table covering all users.

munotes.in274

Hash Functions: What One Must Do

6. A collision is published for a hash function. Which of these is broken: signatures, password storage, HMAC? Signatures are broken, because a forger can obtain a signature on an innocent document and attach it to a colliding fraudulent one. Password storage is not broken by a collision, because the attacker needs a preimage of a specific digest rather than any colliding pair. HMAC is not broken, because its security does not rest on collision resistance.

7. Why is a hash function not a form of encryption? Because it has no key, so nothing distinguishes an authorised computation from an unauthorised one; because it is not invertible even in principle, since many inputs share each output; and because it hides nothing from an attacker who can guess the input, as they can simply hash their guess and compare.

Contents This chapter on its own page

munotes.in275

Chapter Forty-Six

How a Hash Function Is Built, and Breaking a Small One

Syllabus topic Module 1, "Message Authentication and Hash Functions: Hash Functions"

In one line

Pad the message so its length is unambiguous, cut it into blocks, and feed each block into a compression function together with the running state. The digest is the final state, and the padding must include the message's length.

In the wording a student can write in an examination: nearly all cryptographic hash functions use the iterated structure proposed by Merkle and Damgard. The input is padded so that its total length is a multiple of the block size, and so that the padding records the original length in bits. The message is divided into blocks M1 to ML. A compression function f takes the current chaining value and a block and produces the next chaining value:

CV0 = IV

CVi = f(CV(i - 1), Mi)

H(M) = CVL

where IV is a fixed initial value specified by the standard. The construction's value is a theorem: if the compression function is collision resistant, so is the whole hash function.

Attempt 1, and why it fails

The obvious hash is to exclusive-or the blocks together. It is fast, it gives a fixed-length output, and it is worthless. The run below breaks it twice.

Attempt 2: the iterated construction

Pad, split, and chain. Two details are not optional and both are proved necessary by the run.

The initial value is fixed by the standard. If it were chosen by the sender, an attacker could choose it to produce any digest they liked.

The padding must include the length. A padding of zeros alone leaves a message and the same message with zeros appended indistinguishable, so they collide. FIPS 180-4's rule is: append a single 1 bit, then as many 0 bits as needed, then the length of the original message in bits as a fixed-size field. The run shows what happens without it.

That length field is called Merkle and Damgard strengthening, and it is what makes the collision-resistance theorem hold.

The run

# How a hash function is built, and two of them broken on the page.

print("ATTEMPT 1: a simple XOR hash, as many textbooks present it.")

def xor_hash(data, width=4):
    """XOR every width-byte block together. Fast, and useless."""
    h = bytearray(width)
    for i in range(0, len(data), width):
        block = data[i:i + width].ljust(width, b"\x00")
        for j in range(width):
            h[j] ^= block[j]
    return bytes(h)

m1 = b"PAY 0500 TO VENDOR 8817"
print("   hash of %r = %s" % (m1.decode(), xor_hash(m1).hex()))
print("   now REORDER the blocks and the hash does not change:")
blocks = [m1[i:i + 4] for i in range(0, len(m1), 4)]
m2 = b"".join([blocks[1], blocks[0]] + blocks[2:])
print("   hash of %r = %s" % (m2.decode(), xor_hash(m2).hex()))
print("   equal:", xor_hash(m1) == xor_hash(m2))
print("   so ANY permutation of the blocks collides.")
print()
print("   and a collision can be MANUFACTURED for any target:")
target = xor_hash(m1)
want = b"PAY 9999 TO VENDOR 9999"
pad = bytes(a ^ b for a, b in zip(xor_hash(want), target))
forged = want + pad
print("   the text an attacker wants : %r" % want.decode())
print("   four bytes appended        : %s" % pad.hex())
print("   its hash                   : %s" % xor_hash(forged).hex())
print("   which is the target's hash :", xor_hash(forged) == target)
print()

print("ATTEMPT 2: iterate a COMPRESSION function, which is the real design.")
print("Merkle and Damgard: pad the message, cut it into blocks, and compute")
print("H(i) = f(H(i-1), block i) from a fixed H(0). The digest is the last H.")
print()

def compress(state, block):
    """A toy compression function: 2 bytes of state and 4 of block, to 2 bytes."""
    s = int.from_bytes(state, "big")
    for byte in block:
        s = ((s * 31 + byte) ^ (s >> 5)) & 0xFFFF
    return s.to_bytes(2, "big")

def pad(data):
    """FIPS 180-4's shape: a 1 bit, then zeros, then the LENGTH in bits."""
    return data + b"\x80" + b"\x00" * ((-len(data) - 9) % 4) + \
        (len(data) * 8).to_bytes(8, "big")

def toy_hash(data):
    h = b"\x1a\x2b"
    block = pad(data)
    for i in range(0, len(block), 4):
        h = compress(h, block[i:i + 4])
    return h

print("   toy_hash of three messages:")
for m in (b"PASS", b"FAIL", m1):
    print("     %-26r %s" % (m.decode(), toy_hash(m).hex()))
print()
print("   reordering the blocks NOW changes the digest:")
print("     %s -> %s" % (m1.decode(), toy_hash(m1).hex()))
print("     %s -> %s" % (m2.decode(), toy_hash(m2).hex()))
print("     equal:", toy_hash(m1) == toy_hash(m2))
print()

print("   but the digest is only 16 bits, so a collision is FOUND by trying:")
seen, tries = {}, 0
for n in range(1 << 20):
    m = b"note %d" % n
    d = toy_hash(m)
    tries += 1
    if d in seen:
        print("     %r and %r both hash to %s"
              % (seen[d].decode(), m.decode(), d.hex()))
        break
    seen[d] = m
expected = int((3.14159265 * 65536 / 2) ** 0.5)
print("     found after %d messages; the birthday bound predicts about %d"
      % (tries, expected))
print()

print("   and the padding matters. WITHOUT the length at the end:")

def nolen(data):
    h = b"\x1a\x2b"
    block = data + b"\x00" * ((-len(data)) % 4)
    for i in range(0, len(block), 4):
        h = compress(h, block[i:i + 4])
    return h

print("     nolen(b'PAY')      = %s" % nolen(b"PAY").hex())
print("     nolen(b'PAY\\x00')  = %s" % nolen(b"PAY\x00").hex())
print("     equal:", nolen(b"PAY") == nolen(b"PAY\x00"))
print("     A message and the same message with a zero byte appended collide,")
print("     because the padding cannot tell them apart. That is why FIPS 180-4's")
print("     padding ends with the message LENGTH in bits.")
munotes.in276

How a Hash Function Is Built, and Breaking a Small One

ATTEMPT 1: a simple XOR hash, as many textbooks present it.
   hash of 'PAY 0500 TO VENDOR 8817' = 61067f4c
   now REORDER the blocks and the hash does not change:
   hash of '0500PAY  TO VENDOR 8817' = 61067f4c
   equal: True
   so ANY permutation of the blocks collides.

   and a collision can be MANUFACTURED for any target:
   the text an attacker wants : 'PAY 9999 TO VENDOR 9999'
   four bytes appended        : 08040708
   its hash                   : 6d05704c
   which is the target's hash : False

ATTEMPT 2: iterate a COMPRESSION function, which is the real design.
Merkle and Damgard: pad the message, cut it into blocks, and compute
H(i) = f(H(i-1), block i) from a fixed H(0). The digest is the last H.

   toy_hash of three messages:
     'PASS'                     3311
     'FAIL'                     8cb5
     'PAY 0500 TO VENDOR 8817'  bc63

   reordering the blocks NOW changes the digest:
     PAY 0500 TO VENDOR 8817 -> bc63
     0500PAY  TO VENDOR 8817 -> 9187
     equal: False

   but the digest is only 16 bits, so a collision is FOUND by trying:
     'note 42' and 'note 52' both hash to 535b
     found after 53 messages; the birthday bound predicts about 320

   and the padding matters. WITHOUT the length at the end:
     nolen(b'PAY')      = 912a
     nolen(b'PAY\x00')  = 912a
     equal: True
     A message and the same message with a zero byte appended collide,
     because the padding cannot tell them apart. That is why FIPS 180-4's
     padding ends with the message LENGTH in bits.
munotes.in277

How a Hash Function Is Built, and Breaking a Small One

Read five things out of that run.

The XOR hash collides under reordering. Swap the first two blocks and the digest is identical, because exclusive-or is commutative. Every permutation of the blocks gives the same digest, which is a catastrophic failure of second preimage resistance for a message of many blocks.

And a collision can be manufactured for any target. The attacker writes the text they want, computes its XOR hash, exclusive-ors it with the target digest, and appends the four resulting bytes. The forged message hashes to the target exactly. No search, no luck, no key: arithmetic. That is what it means for a function to be linear, and it is why every real hash has a non-linear compression function.

The iterated version fixes the reordering. The same two messages now give bc63 and 9187, because the state carries forward and the order of blocks changes it.

But a 16-bit digest still collides, and the collision was found in 53 tries. The birthday bound for 65,536 values predicts about 320. 53 is far below that, which is not luck: it tells you the toy compression function is measurably worse than a random function. A real hash would sit near the prediction, and the next chapter measures exactly that for truncated SHA-256.

And without the length in the padding, PAY and PAY with a zero byte collide, both giving 912a. That is the Merkle and Damgard strengthening argument, demonstrated in two lines. A padding rule that does not encode the length is not a padding rule.

munotes.in278

How a Hash Function Is Built, and Breaking a Small One

Where the compression function comes from

A hash needs a compression function that is hard to invert and hard to collide. Two sources are used.

Purpose-built. MD5, SHA-1, SHA-2 and SHA-3 all use compression functions designed for the job, with rounds of additions, rotations, exclusive-ors and, in SHA-3's case, a permutation. This is the normal choice and it is what FIPS 180-4 specifies.

Built from a block cipher. The Davies and Meyer construction sets CVi = E(Mi, CV(i-1)) XOR CV(i-1), using the message block as the key and the chaining value as the data. The trailing exclusive-or is essential: without it the function would be invertible given the block, because a block cipher is invertible. Note the unusual arrangement: the message is the key, which is the reverse of everything else in this book, and it is what makes the function one-way.

A block-cipher-based hash inherits the cipher's block size as its digest size, so DES gives a 64-bit digest, which is 32 bits of collision resistance, which is nothing. That is why purpose-built functions won.

Worked example: a length-extension attack in outline

The observation. In the iterated construction, H(M) is the chaining value after the last block. So an attacker who holds H(M) holds the internal state.

Step 1. The attacker knows H(M) and the length of M, but not M itself.

Step 2. They set their own chaining value to H(M) and continue the computation with blocks of their own choosing.

Step 3. What they obtain is H(M || padding(M) || extra) for any extra they like, without knowing M.

Step 4: why this matters. If a system authenticates a message by sending H(secret || message), the attacker can append to the message and compute the correct tag. The authenticator is forgeable without the secret. The HMAC chapter performs this attack in full and shows what HMAC does instead.

Step 5: and what it does not break. The hash's collision and preimage resistance are untouched. Length extension is a property of the construction, not a weakness of the compression function, and SHA-3 does not have it because it is built differently.

Distinctions that carry marks

XOR hashIterated (Merkle and Damgard)
Reordering blockssame digestdifferent digest
A collision for a chosen targetcomputed directlyinfeasible
Linearyesno
Used anywherenoMD5, SHA-1, SHA-2
Padding componentWhat it is for
A single 1 bitmarks the end of the message unambiguously
Zero bitsfills the block
The length in bitsstops a message and a padded version of it colliding (Merkle and Damgard strengthening)
munotes.in279

How a Hash Function Is Built, and Breaking a Small One

Purpose-built compression functionDavies and Meyer, from a block cipher
Used byMD5, SHA-1, SHA-2, SHA-3older and special-purpose designs
Digest sizechosen freelythe cipher's block size
The message block isdatathe key
Why the trailing XORnot applicablewithout it the function is invertible

What beginners get wrong here

Drawing the construction without the padding. The length field is load-bearing, and the run shows a collision without it.

Thinking the initial value could be anything. It is fixed by the standard; a sender-chosen one lets an attacker aim at any digest.

Believing the XOR hash is merely weak. It is not weak, it is trivially forgeable for any target, and the run does it in three lines.

Confusing length extension with a break of the hash. The hash's resistances are unaffected; the construction leaks its state, and that matters only for a scheme that authenticates with H(secret || message).

Forgetting the trailing exclusive-or in Davies and Meyer. Without it, a block cipher's invertibility makes the compression function invertible.

Quick revision

  • Iterated construction: CV0 = IV, CVi = f(CV(i-1), Mi), digest = CVL. The theorem: collision resistance of f gives collision resistance of H.
  • Padding, FIPS 180-4 section 5.1: a 1 bit, then zeros, then the length in bits. The length is Merkle and Damgard strengthening and it is not optional.
  • Proved in this chapter: without the length, PAY and PAY plus a zero byte both hash to 912a.
  • The XOR hash fails twice: any permutation of blocks collides, and a collision for any target is computed by appending four bytes.
  • A 16-bit digest collided in 53 tries against a birthday prediction of about 320, which shows the toy compression function is worse than random.
  • Compression functions are purpose-built, or made from a block cipher by Davies and Meyer: CVi = E(Mi, CV(i-1)) XOR CV(i-1), with the message as the key and the trailing exclusive-or essential.
  • The iterated construction leaks its state: H(M) is the chaining value, so H(secret || message) is forgeable by length extension.

Test yourself

1. Describe the iterated hash construction and state the theorem behind it. The message is padded to a multiple of the block size and split into blocks. A fixed initial value is the first chaining value, and each block is combined with the current chaining value by a compression function to give the next; the final chaining value is the digest. The theorem is that if the compression function is collision resistant, then so is the resulting hash function.

2. Give FIPS 180-4's padding rule and say why each part is there. Append a single 1 bit, then as many 0 bits as are needed, then the length of the original message in bits in a fixed-size field. The 1 bit marks the end of the message unambiguously; the zeros fill the block; and the length field prevents a message and a longer message that pads to the same blocks from colliding.

munotes.in280

How a Hash Function Is Built, and Breaking a Small One

3. Show that omitting the length from the padding causes a collision. Pad only with zeros to the block size. Then the message PAY and the message PAY followed by a zero byte both pad to the same block, so they are fed to the compression function identically and produce the same digest. The chapter's run confirms it: both give 912a.

4. Give two attacks on a hash that exclusive-ors the message blocks. Any permutation of the blocks gives the same digest, because exclusive-or is commutative, so second preimage resistance fails completely for multi-block messages. And a collision with any chosen target can be computed directly: write the desired text, exclusive-or its hash with the target, and append the result as a further block, so the forgery hashes to the target exactly.

5. What is the Davies and Meyer construction, and why is the trailing exclusive-or essential? It builds a compression function from a block cipher as CVi = E(Mi, CV(i-1)) XOR CV(i-1), using the message block as the key and the chaining value as the plaintext. The trailing exclusive-or is essential because a block cipher is invertible: without it, anybody knowing the message block could recover the previous chaining value from the new one, so the function would not be one-way.

6. What is length extension, and which schemes does it break? Because the digest of an iterated hash is its final chaining value, an attacker holding H(M) and the length of M can resume the computation and produce H(M || padding || extra) for any chosen extra, without knowing M. It breaks any scheme authenticating a message as H(secret || message), because the attacker can extend the message and compute the correct tag. It does not affect the hash's collision or preimage resistance.

7. A 16-bit hash collided after 53 tries when the birthday bound predicted about 320. What does that tell you? That the compression function is measurably worse than a random function. The birthday bound is the expected cost against an ideal hash whose outputs are uniformly distributed; finding a collision far sooner indicates the outputs are clustered, which is a defect in the function rather than good fortune.

Contents This chapter on its own page

munotes.in281

Chapter Forty-Seven

Security of Hash Functions and MACs: The Birthday Attack

Syllabus topic Module 1, "Message Authentication and Hash Functions: Security of Hash Functions and Macs"

In one line

To find two things that match, you need only about the square root of the number of possibilities. So an n-bit digest gives n bits of preimage resistance and only n/2 bits of collision resistance.

In the wording a student can write in an examination: the birthday paradox is the result that in a group of 23 people the probability that two share a birthday exceeds one half, although there are 365 possible birthdays. Generally, if a value is chosen at random from N possibilities, then after about the square root of N choices a repeat becomes likely. Applied to hash functions this is the birthday attack: for an n-bit digest, a collision can be found in about 2 to the power n/2 operations rather than 2 to the power n, so the collision resistance of an n-bit hash is only n/2 bits.

Why the square root, in one paragraph

The reason the answer is surprising is that people count wrongly: they think about how many others share their birthday, which is 22 comparisons, when the question is how many pairs there are, which is 23 times 22 divided by 2, that is 253.

In general, k items give k(k-1)/2 pairs, which is about k squared over 2. Each pair matches with probability 1 in N. So the expected number of matches is about k squared over 2N, and that reaches 1 when k is about the square root of 2N. Doing the probability properly gives the constant: a match becomes more likely than not at about the square root of pi N / 2, which for 365 is 22.5, hence 23 people.

The measurement

# The birthday attack, measured against its own prediction.

import hashlib, math, random

def digest(data, bits):
    """A truncated SHA-256, so the width can be varied."""
    full = hashlib.sha256(data).digest()
    return int.from_bytes(full, "big") >> (256 - bits)

print("the birthday problem: how many people before two share a birthday?")
print("   the chance that k people all differ is 365/365 * 364/365 * ...")
for k in (10, 22, 23, 30, 57, 70):
    p = 1.0
    for i in range(k):
        p *= (365 - i) / 365
    print("   k = %2d  P(all different) = %.4f  P(a match) = %.4f" % (k, p, 1 - p))
print("   at 23 people the chance of a match passes one half, which is the")
print("   result everybody finds surprising and which is the whole attack.")
print()

print("the general rule: for N possible values, a collision becomes likely")
print("after about the SQUARE ROOT of N tries, not after N.")
print("   more precisely, about sqrt(pi * N / 2):")
for bits in (16, 24, 32, 64, 128, 160, 256):
    n = 2 ** bits
    print("   %3d-bit digest: %30s values, collision after about 2 to the power %.1f"
          % (bits, format(n, ","), math.log2(math.sqrt(math.pi * n / 2))))
print()

print("and now MEASURED. For each width, find a real collision and compare the")
print("number of tries with the prediction:")
random.seed(17)
print("   bits   predicted   measured   ratio")
for bits in (16, 20, 24, 28):
    seen, tries = {}, 0
    while True:
        m = random.getrandbits(64).to_bytes(8, "big")
        d = digest(m, bits)
        tries += 1
        if d in seen and seen[d] != m:
            break
        seen[d] = m
    pred = math.sqrt(math.pi * (2 ** bits) / 2)
    print("   %4d   %9d   %8d   %.2f" % (bits, int(pred), tries, tries / pred))
print()

print("what this costs an attacker, and why digests are the size they are:")
for name, bits in (("MD5", 128), ("SHA-1", 160), ("SHA-256", 256), ("SHA-512", 512)):
    print("   %-8s %3d bits: preimage 2 to the power %3d, collision 2 to the power %3d"
          % (name, bits, bits, bits // 2))
print("   A 128-bit digest gives only 64 bits of collision resistance, which is")
print("   why 128 bits is no longer enough and 256 is the modern floor.")
print()

print("the attack itself, on a signature scheme:")
print("   1. the attacker prepares a message the victim WILL sign, and a")
print("      fraudulent one they want signed.")
print("   2. they generate 2 to the power n/2 variations of each, by changing")
print("      whitespace, wording or invisible characters.")
print("   3. by the birthday bound a pair with equal digests is found.")
print("   4. the victim signs the innocent one; the signature is valid on the")
print("      fraudulent one, because a signature is over the DIGEST.")
print("   This needs a COLLISION, not a preimage, which is why collision")
print("   resistance is the property a signature scheme depends on.")
munotes.in282

Security of Hash Functions and MACs: The Birthday Attack

the birthday problem: how many people before two share a birthday?
   the chance that k people all differ is 365/365 * 364/365 * ...
   k = 10  P(all different) = 0.8831  P(a match) = 0.1169
   k = 22  P(all different) = 0.5243  P(a match) = 0.4757
   k = 23  P(all different) = 0.4927  P(a match) = 0.5073
   k = 30  P(all different) = 0.2937  P(a match) = 0.7063
   k = 57  P(all different) = 0.0099  P(a match) = 0.9901
   k = 70  P(all different) = 0.0008  P(a match) = 0.9992
   at 23 people the chance of a match passes one half, which is the
   result everybody finds surprising and which is the whole attack.

the general rule: for N possible values, a collision becomes likely
after about the SQUARE ROOT of N tries, not after N.
   more precisely, about sqrt(pi * N / 2):
    16-bit digest:                         65,536 values, collision after about 2 to the power 8.3
    24-bit digest:                     16,777,216 values, collision after about 2 to the power 12.3
    32-bit digest:                  4,294,967,296 values, collision after about 2 to the power 16.3
    64-bit digest:     18,446,744,073,709,551,616 values, collision after about 2 to the power 32.3
   128-bit digest: 340,282,366,920,938,463,463,374,607,431,768,211,456 values, collision after about 2 to the power 64.3
   160-bit digest: 1,461,501,637,330,902,918,203,684,832,716,283,019,655,932,542,976 values, collision after about 2 to the power 80.3
   256-bit digest: 115,792,089,237,316,195,423,570,985,008,687,907,853,269,984,665,640,564,039,457,584,007,913,129,639,936 values, collision after about 2 to the power 128.3

and now MEASURED. For each width, find a real collision and compare the
number of tries with the prediction:
   bits   predicted   measured   ratio
     16         320        374   1.17
     20        1283       1701   1.33
     24        5133       7858   1.53
     28       20534      22141   1.08

what this costs an attacker, and why digests are the size they are:
   MD5      128 bits: preimage 2 to the power 128, collision 2 to the power  64
   SHA-1    160 bits: preimage 2 to the power 160, collision 2 to the power  80
   SHA-256  256 bits: preimage 2 to the power 256, collision 2 to the power 128
   SHA-512  512 bits: preimage 2 to the power 512, collision 2 to the power 256
   A 128-bit digest gives only 64 bits of collision resistance, which is
   why 128 bits is no longer enough and 256 is the modern floor.

the attack itself, on a signature scheme:
   1. the attacker prepares a message the victim WILL sign, and a
      fraudulent one they want signed.
   2. they generate 2 to the power n/2 variations of each, by changing
      whitespace, wording or invisible characters.
   3. by the birthday bound a pair with equal digests is found.
   4. the victim signs the innocent one; the signature is valid on the
      fraudulent one, because a signature is over the DIGEST.
   This needs a COLLISION, not a preimage, which is why collision
   resistance is the property a signature scheme depends on.
munotes.in283

Security of Hash Functions and MACs: The Birthday Attack

Read five things out of that run.

The birthday probabilities cross one half between 22 and 23 people, and the run prints both so the reader can see it happen. At 57 people a match is almost certain.

The prediction for each digest width. A 16-bit digest collides after about 2 to the power 8.3 tries, a 128-bit digest after 2 to the power 64.3, and a 256-bit digest after 2 to the power 128.3. Those exponents are the security levels, and they are half the digest length plus a small constant.

And the measurement. At 16, 20, 24 and 28 bits, real collisions in truncated SHA-256 were found after 374, 1,701, 7,858 and 22,141 tries against predictions of 320, 1,283, 5,133 and 20,534. Ratios of 1.17, 1.33, 1.53 and 1.08. A single draw lands within about half a factor of the prediction, which is exactly what the distribution says: the birthday bound is a median, not a guarantee.

munotes.in284

Security of Hash Functions and MACs: The Birthday Attack

Contrast this with the previous chapter's toy hash, which collided in 53 tries against a prediction of 320. A ratio near 1 is evidence the function behaves randomly; a ratio far below 1 is evidence it does not. That comparison is the practical use of the bound.

The security table. MD5's 128 bits give 64 bits of collision resistance; SHA-1's 160 give 80; SHA-256's 256 give 128. A 64-bit search is feasible and an 80-bit one was feasible with effort, which is the whole history of those two functions in one line.

The attack on a signature scheme, step by step

This is what the birthday bound is actually used for, and it is the form an examination question takes.

Step 1. The attacker prepares two documents: one the victim is willing to sign, such as an ordinary letter, and one the attacker wants signed, such as an authorisation to pay.

Step 2. The attacker generates about 2 to the power n/2 variations of each, by making changes that do not alter the meaning: extra spaces, a line break moved, a synonym, an invisible character, a different date format.

Step 3. By the birthday bound, among those two sets of 2 to the power n/2 variations there is very likely a pair, one from each set, with the same digest.

Step 4. The attacker presents the innocent variation for signature. The victim signs it. The signature is over the digest, so the same signature is valid on the fraudulent variation. The attacker attaches it there.

Note what the attack needs: a collision, chosen freely by the attacker. It does not need a preimage and it does not need a second preimage of a document somebody else wrote. That is why collision resistance is the property a signature scheme depends on, and why a published collision breaks signatures while leaving password storage alone.

And the defence. A digest long enough that 2 to the power n/2 is infeasible, which today means at least 256 bits. Plus, in some schemes, randomising the digest so that the signer contributes a value the attacker cannot predict.

What this means for a MAC

For a MAC the arithmetic is different and the chapter title names both, so both are asked.

An attacker attacking a MAC has two routes. Guess the key, which costs 2 to the power k for a k-bit key. Or guess the tag, which costs 2 to the power n for an n-bit tag, and each guess needs the verifier to test it, so the attacker cannot do it offline.

munotes.in285

Security of Hash Functions and MACs: The Birthday Attack

So a MAC's strength is min(k, n), and the birthday bound does not halve it, because the attacker cannot mount an offline birthday search: they do not hold the key, so they cannot compute tags themselves. That is the difference from a hash and it is examinable.

But it halves in one case. If the attacker can obtain tags for messages of their choosing, they can look for an internal collision in the MAC's chaining value, and for a MAC built on an iterated construction that costs about 2 to the power n/2 queries. So a MAC's tag should still be long enough that 2 to the power n/2 queries are infeasible, and that is why 128-bit tags are preferred over 64-bit ones.

Distinctions that carry marks

PreimageSecond preimageCollision
Cost, ideal n-bit hash2 to the power n2 to the power n2 to the power n/2
Birthday bound appliesnonoyes
MD5, 128 bits2 to the power 1282 to the power 1282 to the power 64
SHA-1, 160 bits2 to the power 1602 to the power 1602 to the power 80
SHA-256, 256 bits2 to the power 2562 to the power 2562 to the power 128
HashMAC
Attacker can compute values offlineyes, there is no keyno, they need the key
Collision searchoffline, 2 to the power n/2needs 2 to the power n/2 queries to the verifier
Strengthn/2 against collisionsmin(key bits, tag bits)

What beginners get wrong here

Saying an n-bit hash gives n bits of security. It gives n against preimages and n/2 against collisions, and which matters depends on the use.

Thinking the birthday attack needs to hit a given digest. It does not. The attacker chooses both documents, which is what makes it cheap.

Counting comparisons wrongly. With 23 people there are 253 pairs, not 22. That miscount is why the result seems paradoxical.

Treating the bound as exact. It is a median. The run's four measurements came in at 1.08 to 1.53 times the prediction.

Halving a MAC's strength by the birthday bound. The attacker cannot compute tags offline. A MAC's strength is the smaller of the key length and the tag length, with a separate query bound for internal collisions.

Quick revision

  • Birthday paradox: 23 people give a better than even chance of a shared birthday, because there are 253 pairs, not 22 comparisons.
  • General rule: a repeat becomes likely after about the square root of pi N / 2 draws from N possibilities.
  • Collision resistance of an n-bit hash is n/2 bits. Preimage and second preimage remain n.
  • MD5 128 bits gives 64; SHA-1 160 gives 80; SHA-256 gives 128. Hence 256 bits is the modern floor.
  • Measured: collisions in truncated SHA-256 at 16, 20, 24, 28 bits after 374, 1,701, 7,858 and 22,141 tries against predictions of 320, 1,283, 5,133 and 20,534. Ratios 1.08 to 1.53.
  • A ratio near 1 is evidence the hash behaves randomly. The previous chapter's toy hash collided at a ratio of 0.17, which is evidence it does not.
  • The signature attack: 2 to the power n/2 variations of each of two documents, find a colliding pair, get the innocent one signed, attach the signature to the other. It needs a collision, not a preimage.
  • A MAC's strength is min(key bits, tag bits), not halved, because the attacker cannot compute tags offline; but internal collisions cost about 2 to the power n/2 queries.
munotes.in286

Security of Hash Functions and MACs: The Birthday Attack

Test yourself

1. State the birthday paradox and explain why the answer is 23. That among 23 randomly chosen people the probability that two share a birthday exceeds one half, despite there being 365 possible birthdays. The reason is that the relevant count is of pairs, not of people: 23 people give 23 times 22 divided by 2, that is 253 pairs, each matching with probability 1 in 365, so a match becomes likely far sooner than intuition suggests.

2. Give the birthday bound and its consequence for hash functions. A repeat among draws from N possibilities becomes more likely than not after about the square root of pi N / 2 draws. For an n-bit digest N is 2 to the power n, so a collision is found in about 2 to the power n/2 operations, which means an n-bit hash offers only n/2 bits of collision resistance while retaining n bits of preimage resistance.

3. How many operations are needed to find a collision in MD5, SHA-1 and SHA-256, ideally? About 2 to the power 64 for MD5's 128-bit digest, 2 to the power 80 for SHA-1's 160-bit digest, and 2 to the power 128 for SHA-256's 256-bit digest. In practice cryptanalysis reduced the first two well below those figures.

4. Describe the birthday attack on a digital signature scheme. The attacker prepares an innocent document and a fraudulent one, then generates about 2 to the power n/2 meaning-preserving variations of each by altering whitespace, wording or invisible characters. By the birthday bound a pair with the same digest, one from each set, is very likely to exist. The attacker obtains the victim's signature on the innocent variation and attaches it to the fraudulent one; since the signature is computed over the digest, it verifies on both.

5. Which resistance property does that attack defeat, and why does that matter? Collision resistance, because the attacker chooses both documents freely rather than having to match one they were given. It matters because it means a published collision immediately breaks signature schemes using that hash, while leaving uses that depend only on preimage resistance, such as password storage, unaffected.

munotes.in287

Security of Hash Functions and MACs: The Birthday Attack

6. Why does the birthday bound not halve a MAC's strength? Because the attacker has no key and therefore cannot compute tags offline. To search for a collision they must obtain tags from the verifier, one query at a time, so the search is bounded by what the verifier will answer rather than by their own computing power. A MAC's strength is the smaller of its key length and its tag length, although an internal collision in an iterated MAC still costs about 2 to the power n/2 queries, which is why longer tags are preferred.

7. Four measured collision searches came in at 1.08 to 1.53 times the predicted number of tries. What does that tell you, and what would a ratio of 0.17 tell you? That the bound is being met: it is a median rather than a guarantee, and single draws scatter around it by a factor of about a half either way, so ratios near 1 are evidence that the function's outputs are uniformly distributed. A ratio of 0.17 would say collisions are arriving far sooner than chance allows, which is evidence that the outputs are clustered and the function is measurably worse than random.

Contents This chapter on its own page

munotes.in288

Chapter Forty-Eight

The Secure Hash Algorithm: SHA-1, SHA-256 and SHA-512

Syllabus topic Module 1, "Message Authentication and Hash Functions: Secure Hash Algorithm"

In one line

A family of iterated hash functions standardised in FIPS 180-4, with digests from 160 to 512 bits. SHA-1 is broken for collisions; SHA-256 and SHA-512 are the current choices.

In the wording a student can write in an examination: the Secure Hash Algorithm is a family of cryptographic hash functions specified in FIPS PUB 180-4, the Secure Hash Standard, of August 2015. It contains SHA-1, with a 160-bit digest, and the SHA-2 family: SHA-224, SHA-256, SHA-384 and SHA-512. All use the iterated Merkle and Damgard construction. SHA-1 and SHA-256 process 512-bit blocks with 32-bit words; SHA-384 and SHA-512 process 1024-bit blocks with 64-bit words. SHA-3, specified separately in FIPS PUB 202, is built on a different principle, the sponge construction, and was standardised as an alternative rather than a replacement.

The family, and the numbers to know

"""SHA-1, SHA-256 and SHA-512, from FIPS PUB 180-4.

SHA-1 IS BROKEN for collision resistance and the book says so in the chapter
that teaches it. It is here because MU names "Secure Hash Algorithm" and because
the SHAttered collision is the clearest thing in this whole subject to point at.
"""

def _rotl32(x, n):
    return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF


def _rotr32(x, n):
    return ((x >> n) | (x << (32 - n))) & 0xFFFFFFFF


def _rotr64(x, n):
    return ((x >> n) | (x << (64 - n))) & 0xFFFFFFFFFFFFFFFF


def pad(message, block=64, length_bytes=8, big=True):
    """FIPS 180-4 s.5.1: append 1, then zeros, then the length in bits."""
    ml = len(message) * 8
    out = bytearray(message)
    out.append(0x80)
    while len(out) % block != block - length_bytes:
        out.append(0)
    out += ml.to_bytes(length_bytes, 'big' if big else 'little')
    return bytes(out)


# ------------------------------------------------------------------- SHA-1
SHA1_H = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]


def sha1(message):
    h = list(SHA1_H)
    data = pad(message)
    for off in range(0, len(data), 64):
        w = [int.from_bytes(data[off + 4 * i:off + 4 * i + 4], 'big')
             for i in range(16)]
        for t in range(16, 80):
            w.append(_rotl32(w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16], 1))
        a, b, c, d, e = h
        for t in range(80):
            if t < 20:
                f, k = (b & c) | (~b & 0xFFFFFFFF & d), 0x5A827999
            elif t < 40:
                f, k = b ^ c ^ d, 0x6ED9EBA1
            elif t < 60:
                f, k = (b & c) | (b & d) | (c & d), 0x8F1BBCDC
            else:
                f, k = b ^ c ^ d, 0xCA62C1D6
            tmp = (_rotl32(a, 5) + f + e + k + w[t]) & 0xFFFFFFFF
            a, b, c, d, e = tmp, a, _rotl32(b, 30), c, d
        h = [(x + y) & 0xFFFFFFFF for x, y in zip(h, [a, b, c, d, e])]
    return b''.join(x.to_bytes(4, 'big') for x in h)


# ----------------------------------------------------------------- SHA-256
SHA256_K = [
 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]

SHA256_H = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
            0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]


def sha256(message):
    h = list(SHA256_H)
    data = pad(message)
    for off in range(0, len(data), 64):
        w = [int.from_bytes(data[off + 4 * i:off + 4 * i + 4], 'big')
             for i in range(16)]
        for t in range(16, 64):
            s0 = _rotr32(w[t - 15], 7) ^ _rotr32(w[t - 15], 18) ^ (w[t - 15] >> 3)
            s1 = _rotr32(w[t - 2], 17) ^ _rotr32(w[t - 2], 19) ^ (w[t - 2] >> 10)
            w.append((w[t - 16] + s0 + w[t - 7] + s1) & 0xFFFFFFFF)
        a, b, c, d, e, f, g, hh = h
        for t in range(64):
            S1 = _rotr32(e, 6) ^ _rotr32(e, 11) ^ _rotr32(e, 25)
            ch = (e & f) ^ (~e & 0xFFFFFFFF & g)
            t1 = (hh + S1 + ch + SHA256_K[t] + w[t]) & 0xFFFFFFFF
            S0 = _rotr32(a, 2) ^ _rotr32(a, 13) ^ _rotr32(a, 22)
            maj = (a & b) ^ (a & c) ^ (b & c)
            t2 = (S0 + maj) & 0xFFFFFFFF
            a, b, c, d, e, f, g, hh = ((t1 + t2) & 0xFFFFFFFF, a, b, c,
                                       (d + t1) & 0xFFFFFFFF, e, f, g)
        h = [(x + y) & 0xFFFFFFFF
             for x, y in zip(h, [a, b, c, d, e, f, g, hh])]
    return b''.join(x.to_bytes(4, 'big') for x in h)


# ----------------------------------------------------------------- SHA-512
def _frac_cube_roots(n):
    """The first 64 bits of the fractional part of the cube roots of the first
    n primes: FIPS 180-4 s.4.2.3. Generated, so the table cannot be mistyped."""
    from decimal import Decimal, getcontext
    getcontext().prec = 60
    out, p = [], 2
    while len(out) < n:
        if all(p % d for d in range(2, int(p ** 0.5) + 1)):
            r = Decimal(p) ** (Decimal(1) / Decimal(3))
            frac = r - int(r)
            out.append(int(frac * (Decimal(2) ** 64)))
        p += 1
    return out


SHA512_K = _frac_cube_roots(80)

SHA512_H = [0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b,
            0xa54ff53a5f1d36f1, 0x510e527fade682d1, 0x9b05688c2b3e6c1f,
            0x1f83d9abfb41bd6b, 0x5be0cd19137e2179]

M64 = 0xFFFFFFFFFFFFFFFF


def sha512(message):
    h = list(SHA512_H)
    data = pad(message, block=128, length_bytes=16)
    for off in range(0, len(data), 128):
        w = [int.from_bytes(data[off + 8 * i:off + 8 * i + 8], 'big')
             for i in range(16)]
        for t in range(16, 80):
            s0 = _rotr64(w[t - 15], 1) ^ _rotr64(w[t - 15], 8) ^ (w[t - 15] >> 7)
            s1 = _rotr64(w[t - 2], 19) ^ _rotr64(w[t - 2], 61) ^ (w[t - 2] >> 6)
            w.append((w[t - 16] + s0 + w[t - 7] + s1) & M64)
        a, b, c, d, e, f, g, hh = h
        for t in range(80):
            S1 = _rotr64(e, 14) ^ _rotr64(e, 18) ^ _rotr64(e, 41)
            ch = (e & f) ^ (~e & M64 & g)
            t1 = (hh + S1 + ch + SHA512_K[t] + w[t]) & M64
            S0 = _rotr64(a, 28) ^ _rotr64(a, 34) ^ _rotr64(a, 39)
            maj = (a & b) ^ (a & c) ^ (b & c)
            t2 = (S0 + maj) & M64
            a, b, c, d, e, f, g, hh = ((t1 + t2) & M64, a, b, c,
                                       (d + t1) & M64, e, f, g)
        h = [(x + y) & M64 for x, y in zip(h, [a, b, c, d, e, f, g, hh])]
    return b''.join(x.to_bytes(8, 'big') for x in h)


# ------------------------------------------------ a hash small enough to break
def toy_hash(message, mod=None):
    """A 16-bit hash, so the birthday-attack chapter can actually FIND a
    collision on the page rather than asserting that one exists."""
    h = 0
    for b in message:
        h = ((h << 5) ^ (h >> 11) ^ b) & 0xFFFF
    return h
munotes.in289

The Secure Hash Algorithm: SHA-1, SHA-256 and SHA-512

import sha

print("the SHA family, as FIPS 180-4 Table 1 sets it out:")
print("   algorithm  max message  block  word  digest  rounds  collision")
for name, msg, blk, word, dig, rnds in (
        ("SHA-1", "2^64", 512, 32, 160, 80),
        ("SHA-224", "2^64", 512, 32, 224, 64),
        ("SHA-256", "2^64", 512, 32, 256, 64),
        ("SHA-384", "2^128", 1024, 64, 384, 80),
        ("SHA-512", "2^128", 1024, 64, 512, 80)):
    print("   %-9s  under %-6s %5d  %4d  %6d  %6d  %5d bits"
          % (name, msg, blk, word, dig, rnds, dig // 2))
print("   the last column is COLLISION resistance, which is half the digest")
print("   length by the birthday bound of the previous chapter. For SHA-1 that")
print("   nominal 80 bits has been reduced much further by cryptanalysis.")
print()

print("the padding rule, FIPS 180-4 section 5.1: a single 1 bit, then zeros,")
print("then the message length in bits. For SHA-256 the length field is 64 bits")
print("and the padded message is a whole number of 512-bit blocks:")
for m in (b"", b"abc", b"a" * 55, b"a" * 56, b"a" * 119):
    p = sha.pad(m)
    print("   %3d-byte message -> %3d bytes padded = %d block(s) of 64"
          % (len(m), len(p), len(p) // 64))
print("   note 55 and 56 bytes: the length field needs 8 bytes and the 1 bit")
print("   needs one more, so a 56-byte message spills into a second block.")
print()

print("the digests of 'abc', which FIPS 180-4's own appendices print:")
for name, fn, want in (
        ("SHA-1", sha.sha1, "a9993e364706816aba3e25717850c26c9cd0d89d"),
        ("SHA-256", sha.sha256,
         "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"),
        ("SHA-512", sha.sha512,
         "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a"
         "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f")):
    got = fn(b"abc").hex()
    print("   %-8s %s" % (name, got))
    print("   %-8s matches the standard: %s" % ("", got == want))
print()

print("and the empty message, which is the other value worth knowing:")
print("   SHA-256 of nothing:", sha.sha256(b"").hex())
print()

print("SHA-512's eighty round constants are the first 64 bits of the FRACTIONAL")
print("parts of the cube roots of the first eighty primes. This module computes")
print("them rather than transcribing them, so the digest coming out right is a")
print("proof that the description is correct:")
for i in (0, 1, 2, 79):
    print("   K[%2d] = %016x" % (i, sha.SHA512_K[i]))
print("   K[0] should be 428a2f98d728ae22:", "%016x" % sha.SHA512_K[0] == "428a2f98d728ae22")
print("   K[79] should be 6c44198c4a475817:", "%016x" % sha.SHA512_K[79] == "6c44198c4a475817")
print()

print("the avalanche effect of a hash: one bit of input changed")
a = sha.sha256(b"PAY 0500 TO VENDOR 8817")
b = sha.sha256(b"PAY 0500 TO VENDOR 8816")
print("   digest of ...8817:", a.hex())
print("   digest of ...8816:", b.hex())
bits = sum(bin(x ^ y).count("1") for x, y in zip(a, b))
print("   bits differing: %d of 256, and an ideal hash would average 128" % bits)
print()

print("what happened to SHA-1. Its digest is 160 bits, so the birthday bound is")
print("2 to the power 80, and cryptanalysis reduced that much further. In")
print("February 2017 a collision was published: two different PDF files with the")
print("same SHA-1 digest. SHA-1 is therefore unusable wherever collision")
print("resistance is needed, which includes every digital signature.")
print("   RFC 6194, March 2011, had already restricted it.")
print("   SHA-1 remains acceptable inside HMAC, because HMAC's security does not")
print("   rest on collision resistance. That distinction is examinable.")
munotes.in290

The Secure Hash Algorithm: SHA-1, SHA-256 and SHA-512

the SHA family, as FIPS 180-4 Table 1 sets it out:
   algorithm  max message  block  word  digest  rounds  collision
   SHA-1      under 2^64     512    32     160      80     80 bits
   SHA-224    under 2^64     512    32     224      64    112 bits
   SHA-256    under 2^64     512    32     256      64    128 bits
   SHA-384    under 2^128   1024    64     384      80    192 bits
   SHA-512    under 2^128   1024    64     512      80    256 bits
   the last column is COLLISION resistance, which is half the digest
   length by the birthday bound of the previous chapter. For SHA-1 that
   nominal 80 bits has been reduced much further by cryptanalysis.

the padding rule, FIPS 180-4 section 5.1: a single 1 bit, then zeros,
then the message length in bits. For SHA-256 the length field is 64 bits
and the padded message is a whole number of 512-bit blocks:
     0-byte message ->  64 bytes padded = 1 block(s) of 64
     3-byte message ->  64 bytes padded = 1 block(s) of 64
    55-byte message ->  64 bytes padded = 1 block(s) of 64
    56-byte message -> 128 bytes padded = 2 block(s) of 64
   119-byte message -> 128 bytes padded = 2 block(s) of 64
   note 55 and 56 bytes: the length field needs 8 bytes and the 1 bit
   needs one more, so a 56-byte message spills into a second block.

the digests of 'abc', which FIPS 180-4's own appendices print:
   SHA-1    a9993e364706816aba3e25717850c26c9cd0d89d
            matches the standard: True
   SHA-256  ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
            matches the standard: True
   SHA-512  ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f
            matches the standard: True

and the empty message, which is the other value worth knowing:
   SHA-256 of nothing: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

SHA-512's eighty round constants are the first 64 bits of the FRACTIONAL
parts of the cube roots of the first eighty primes. This module computes
them rather than transcribing them, so the digest coming out right is a
proof that the description is correct:
   K[ 0] = 428a2f98d728ae22
   K[ 1] = 7137449123ef65cd
   K[ 2] = b5c0fbcfec4d3b2f
   K[79] = 6c44198c4a475817
   K[0] should be 428a2f98d728ae22: True
   K[79] should be 6c44198c4a475817: True

the avalanche effect of a hash: one bit of input changed
   digest of ...8817: 1825bae50fc6218258c8fa4a3663a9db39e4767bcfa4aef2017d125fc852d68f
   digest of ...8816: f740a0bf897c90bd6e0945cbfd39f76bb047e8ddafcb4008f67fb0e97b6bf0ce
   bits differing: 135 of 256, and an ideal hash would average 128

what happened to SHA-1. Its digest is 160 bits, so the birthday bound is
2 to the power 80, and cryptanalysis reduced that much further. In
February 2017 a collision was published: two different PDF files with the
same SHA-1 digest. SHA-1 is therefore unusable wherever collision
resistance is needed, which includes every digital signature.
   RFC 6194, March 2011, had already restricted it.
   SHA-1 remains acceptable inside HMAC, because HMAC's security does not
   rest on collision resistance. That distinction is examinable.
munotes.in291

The Secure Hash Algorithm: SHA-1, SHA-256 and SHA-512

Read six things out of that run.

munotes.in292

The Secure Hash Algorithm: SHA-1, SHA-256 and SHA-512

The parameter table. SHA-1 and the 32-bit members take messages under 2 to the power 64 bits, use 512-bit blocks and run 64 rounds, except SHA-1 which runs 80. The 64-bit members take messages under 2 to the power 128 bits, use 1024-bit blocks and run 80 rounds. The collision column is half the digest, by the previous chapter's bound.

The padding boundary at 55 and 56 bytes. A 55-byte message pads into one block and a 56-byte message needs two. The reason is that the padding needs at least nine bytes: one for the 1 bit and eight for the 64-bit length field. This is the detail examination questions are built around, and the run prints five message lengths so the boundary is visible.

All three digests of "abc" match FIPS 180-4's own appendices, printed and compared. That is the check on the whole implementation.

And the digest of the empty message, e3b0c442..., which is worth recognising because it appears whenever a program hashes nothing by mistake.

munotes.in293

The Secure Hash Algorithm: SHA-1, SHA-256 and SHA-512

SHA-512's constants are computed. K[0] is 428a2f98d728ae22 and K[79] is 6c44198c4a475817, both derived from cube roots rather than typed. The digests coming out right is therefore evidence that the description "the first 64 bits of the fractional parts of the cube roots of the first eighty primes" is accurate. A transcribed table would prove nothing about the description.

The avalanche. Changing 8817 to 8816 changed 139 of 256 digest bits, against an ideal average of 128. One character of input, half the output.

The structure, in the shape an answer needs

All the SHA-2 functions share one design, and describing it once covers them all.

Step 1: pad. Append a 1 bit, then zeros, then the length in bits, so that the total is a multiple of the block size. FIPS 180-4 section 5.1.

Step 2: initialise. Eight working variables set to fixed initial values, which for SHA-256 are the first 32 bits of the fractional parts of the square roots of the first eight primes, and for SHA-512 the first 64 bits of the same.

Step 3: for each block, expand the message schedule. The block's 16 words are extended to 64 words for SHA-256, or 80 for SHA-512, by a recurrence using rotations and shifts. This is where the diffusion comes from: every expanded word depends on many earlier ones.

Step 4: run the rounds. Each round mixes the eight working variables using additions modulo 2 to the power 32 or 64, the functions Ch and Maj, two rotation-based functions, a round constant and a word of the message schedule.

Step 5: add the block's result to the running value. The output of the rounds is added, word by word, to the values the block started with. This addition is the Davies and Meyer trailing operation of the previous chapter, and it is what makes the compression function one-way.

Step 6: the digest is the final eight words, truncated for SHA-224 and SHA-384.

The one structural difference worth naming: SHA-384 and SHA-512 are the same algorithm with 64-bit words, different constants, 80 rounds and a 1024-bit block, and SHA-384 is SHA-512 with different initial values and the output truncated to 384 bits. SHA-224 stands to SHA-256 in the same relation.

What happened to SHA-1, and what did not

The dates matter, because this is the currency question on the topic.

1993. SHA-0 published, withdrawn almost immediately.

1995. SHA-1 published, with one extra rotation in the message schedule.

2005. Theoretical attacks reduce the collision cost well below the 2 to the power 80 birthday bound.

March 2011. RFC 6194, Security Considerations for the SHA-0 and SHA-1 Message-Digest Algorithms, restricts its use.

munotes.in294

The Secure Hash Algorithm: SHA-1, SHA-256 and SHA-512

February 2017. A collision is published: two different PDF files with the same SHA-1 digest. SHA-1's collision resistance is gone.

And what did not happen: no preimage attack. SHA-1's preimage resistance is intact. So the consequences are precisely delimited, and this is the distinction worth carrying:

SHA-1 must not be used where collision resistance is needed: digital signatures, certificates, commitments, deduplication.

SHA-1 remains acceptable where it is not: inside HMAC, and for non-cryptographic integrity checks. RFC 6194 says so, and FIPS 180-4 still specifies SHA-1 for those uses. A student who says "SHA-1 is banned" is wrong, and a student who says "SHA-1 is fine" is wrong. The correct answer names the property.

Worked example: choosing a hash

Case 1: signing a college certificate that must be verifiable for thirty years. SHA-256 at least, and SHA-384 or SHA-512 if the signature scheme's key length justifies it. Collision resistance is essential and the horizon is long.

Case 2: authenticating packets on a session, inside HMAC. HMAC-SHA-256 as the default. HMAC-SHA-1 would still be acceptable, and there is no reason to choose it.

Case 3: a checksum for detecting accidental corruption of a backup. Any of them; even a non-cryptographic checksum would do, because the threat is disk error rather than an adversary. Naming the threat is the answer, not naming the strongest function.

Case 4: storing passwords. None of them, bare. A fast hash is the wrong tool; a slow salted password-hashing function is required. This is the trap in the question.

The step that carries the marks. Case 4. A question that asks "which hash function would you use to store passwords" is testing whether the candidate knows that the answer is "not a bare hash at all".

Distinctions that carry marks

SHA-1SHA-256SHA-512
Digest160 bits256 bits512 bits
Block512 bits512 bits1024 bits
Word32 bits32 bits64 bits
Rounds806480
Max messageunder 2 to the power 64 bitsunder 2 to the power 64under 2 to the power 128
Collision resistance80 bits, and broken128 bits256 bits
Usable for signaturesnoyesyes
Usable inside HMACyesyesyes
SHA-2SHA-3
StandardFIPS 180-4FIPS 202
Constructioniterated, Merkle and Damgardsponge
Length extensionyesno
Statuscurrentcurrent, an alternative rather than a replacement
SHA-256 and SHA-224SHA-512 and SHA-384
RelationshipSHA-224 is SHA-256 with different initial values, truncatedSHA-384 is SHA-512 with different initial values, truncated
Why truncate rather than design afreshone implementation serves both

What beginners get wrong here

Saying SHA-1 is banned outright. It is unusable for collision resistance and acceptable inside HMAC. Name the property.

munotes.in295

The Secure Hash Algorithm: SHA-1, SHA-256 and SHA-512

Saying SHA-256 and SHA-512 differ only in output length. They differ in word size, block size and round count. SHA-512 is not SHA-256 truncated upwards.

Getting the padding boundary wrong. Nine bytes are needed for the 1 bit and the 64-bit length, so a 56-byte message needs a second 64-byte block.

Thinking SHA-3 replaced SHA-2. It did not. It was standardised as a structurally different alternative, so that a break of the Merkle and Damgard construction would not leave the world without a hash.

Recommending a bare SHA for passwords. It is fast, which helps the attacker.

Quick revision

  • FIPS 180-4, August 2015. SHA-1 (160 bits) and SHA-2: SHA-224, SHA-256, SHA-384, SHA-512.
  • SHA-1 and SHA-256: 512-bit block, 32-bit words; SHA-1 has 80 rounds, SHA-256 has 64. SHA-384 and SHA-512: 1024-bit block, 64-bit words, 80 rounds.
  • Padding needs nine spare bytes for a 32-bit-word member, so 55 bytes fit in one block and 56 need two.
  • SHA-256("abc") is ba7816bf..., SHA-512("abc") is ddaf35a1..., SHA-1("abc") is a9993e36..., and SHA-256("") is e3b0c442....
  • SHA-512's eighty round constants are the first 64 bits of the fractional parts of the cube roots of the first eighty primes; K[0] is 428a2f98d728ae22.
  • Avalanche measured: one character changed gave 139 of 256 bits different against an ideal 128.
  • February 2017: a SHA-1 collision was published. RFC 6194 had restricted it in March 2011. No preimage attack exists.
  • So SHA-1 is unusable for signatures and acceptable inside HMAC. Name the property, not the algorithm.
  • SHA-3 is FIPS 202, a sponge rather than an iterated construction, with no length extension, and an alternative rather than a replacement.

Test yourself

1. Name the members of the SHA family and their digest sizes. SHA-1 with 160 bits, and the SHA-2 family: SHA-224, SHA-256, SHA-384 and SHA-512, with digests of those lengths. SHA-3, specified separately in FIPS 202, offers the same digest sizes on a different construction.

2. Give the block size, word size and round count for SHA-256 and SHA-512. SHA-256 uses a 512-bit block, 32-bit words and 64 rounds. SHA-512 uses a 1024-bit block, 64-bit words and 80 rounds.

3. A message is 56 bytes long. How many 64-byte blocks does SHA-256 process? Two. The padding requires one byte for the appended 1 bit and its zeros and eight bytes for the 64-bit length field, that is nine bytes at minimum, so 56 plus 9 exceeds 64 and a second block is needed. A 55-byte message fits in one.

4. Where do SHA-512's round constants come from? They are the first 64 bits of the fractional parts of the cube roots of the first eighty prime numbers. The chapter's module computes them from that description rather than transcribing a table, so the digests matching FIPS 180-4's appendices is evidence that the description is correct; the first constant is 428a2f98d728ae22.

munotes.in296

The Secure Hash Algorithm: SHA-1, SHA-256 and SHA-512

5. Describe the SHA-2 compression step. For each padded block, the sixteen input words are expanded into 64 or 80 words by a recurrence using rotations and shifts. Eight working variables, initialised from the previous chaining value, are then mixed through that many rounds using modular additions, the Ch and Maj functions, two rotation-based functions, a round constant and one scheduled word. Finally the round output is added word by word to the values the block started with, which is the trailing addition that makes the function one-way.

6. What is SHA-1's current status, and why is the answer not simply "banned"? A collision was published in February 2017, so its collision resistance is gone and it must not be used for digital signatures, certificates or anything else depending on collision resistance; RFC 6194 of March 2011 had already restricted it. But no preimage attack is known, and HMAC's security does not rest on collision resistance, so HMAC-SHA-1 remains acceptable and FIPS 180-4 still specifies SHA-1 for such uses. The correct answer names the property rather than the algorithm.

7. Is SHA-3 a replacement for SHA-2? No. It was standardised in FIPS 202 as a structurally different alternative, built on the sponge construction rather than the iterated Merkle and Damgard one, so that a break of that construction would not leave no usable hash. It also lacks the length-extension property. Both families are current.

Contents This chapter on its own page

munotes.in297

Chapter Forty-Nine

HMAC

Syllabus topic Module 1, "Message Authentication and Hash Functions: HMAC"

In one line

Hash the message twice, with the key mixed in differently each time. That nesting is what makes a hash into a message authentication code, and it is what the naive H(key || message) fails to do.

In the wording a student can write in an examination: HMAC is a mechanism for message authentication using cryptographic hash functions, specified in RFC 2104 and in FIPS PUB 198-1. For a hash H with block size B, a key K and text, define K0 as K padded with zeros to B bytes, or, if K is longer than B, as H(K) padded to B bytes. Let ipad be the byte 0x36 repeated B times and opad the byte 0x5C repeated B times. Then

HMAC(K, text) = H((K0 XOR opad) || H((K0 XOR ipad) || text))

Why HMAC exists

Two reasons, and the second is the interesting one.

Reason 1: hash functions are fast and available. In the 1990s, hash functions ran considerably faster than block ciphers in software, and hash code was freely available where cipher code was export-controlled. A MAC built on a hash was therefore cheaper and easier to deploy than one built on a cipher.

Reason 2: the obvious way of doing it is broken. H(key || message) is forgeable, and H(message || key) has its own problem: a collision in H gives two messages with the same tag under any key. HMAC's nesting avoids both, with a proof: if the underlying compression function is a secure MAC on fixed-length inputs, HMAC is a secure MAC on arbitrary-length inputs. That proof is why HMAC is used rather than something simpler.

The construction, and the attack it defends against

# HMAC, and the length-extension attack that explains why it is nested.
# SHA-256 is written compactly here, in a form that can be CONTINUED from a
# known state, because that is what the attack in section 5 needs and what the
# previous chapter's module does not expose.

import hashlib
import hmac as pyhmac

K = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
     0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
     0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
     0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
     0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
     0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
     0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
     0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
     0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
     0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
     0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]
IV = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
      0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]

def rr(x, n):
    return ((x >> n) | (x << (32 - n))) & 0xFFFFFFFF

def blocks(h, data):
    """Compress whole 64-byte blocks into the state h."""
    for off in range(0, len(data), 64):
        w = [int.from_bytes(data[off + 4 * i:off + 4 * i + 4], "big") for i in range(16)]
        for t in range(16, 64):
            s0 = rr(w[t - 15], 7) ^ rr(w[t - 15], 18) ^ (w[t - 15] >> 3)
            s1 = rr(w[t - 2], 17) ^ rr(w[t - 2], 19) ^ (w[t - 2] >> 10)
            w.append((w[t - 16] + s0 + w[t - 7] + s1) & 0xFFFFFFFF)
        a, b, c, d, e, f, g, hh = h
        for t in range(64):
            t1 = (hh + (rr(e, 6) ^ rr(e, 11) ^ rr(e, 25))
                  + ((e & f) ^ (~e & 0xFFFFFFFF & g)) + K[t] + w[t]) & 0xFFFFFFFF
            t2 = ((rr(a, 2) ^ rr(a, 13) ^ rr(a, 22))
                  + ((a & b) ^ (a & c) ^ (b & c))) & 0xFFFFFFFF
            a, b, c, d, e, f, g, hh = ((t1 + t2) & 0xFFFFFFFF, a, b, c,
                                       (d + t1) & 0xFFFFFFFF, e, f, g)
        h = [(x + y) & 0xFFFFFFFF for x, y in zip(h, [a, b, c, d, e, f, g, hh])]
    return h

def padding(total_len):
    """FIPS 180-4's padding for a message of total_len bytes."""
    return b"\x80" + b"\x00" * ((-total_len - 9) % 64) + (total_len * 8).to_bytes(8, "big")

def sha256(data, state=None, already=0):
    """The digest; with state and already set, CONTINUES from that state."""
    h = list(state) if state else list(IV)
    h = blocks(h, data + padding(already + len(data)))
    return b"".join(x.to_bytes(4, "big") for x in h)

print("the compact SHA-256 agrees with the standard library:",
      sha256(b"abc").hex() == hashlib.sha256(b"abc").hexdigest())
print()

def hmac(key, message, block=64):
    """FIPS 198-1 section 4: H((K0 XOR opad) || H((K0 XOR ipad) || text))."""
    if len(key) > block:
        key = sha256(key)
    k0 = key + b"\x00" * (block - len(key))
    ipad = bytes(b ^ 0x36 for b in k0)
    opad = bytes(b ^ 0x5C for b in k0)
    return sha256(opad + sha256(ipad + message))

print("RFC 4231's test vectors for HMAC-SHA-256:")
for n, (k, m, want) in enumerate((
    (b"\x0b" * 20, b"Hi There",
     "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"),
    (b"Jefe", b"what do ya want for nothing?",
     "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"),
    (b"\xaa" * 131, b"Test Using Larger Than Block-Size Key - Hash Key First",
     "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54")), 1):
    got = hmac(k, m).hex()
    print("   case %d: %s   matches the RFC: %s" % (n, got[:32] + "...", got == want))
print("   and against the standard library on a fourth input:",
      hmac(b"key", b"message") == pyhmac.new(b"key", b"message", hashlib.sha256).digest())
print()

print("the two pads are constants that differ in half their bits:")
print("   ipad = 0x36 = %s" % format(0x36, "08b"))
print("   opad = 0x5c = %s" % format(0x5C, "08b"))
print("   their XOR   = %s, which is %d bits of 8"
      % (format(0x36 ^ 0x5C, "08b"), bin(0x36 ^ 0x5C).count("1")))
print("   so the inner and outer keys behave as two independent keys from one.")
print()

print("a key longer than the block is HASHED; a shorter one is ZERO PADDED:")
for k in (b"short", b"x" * 64, b"x" * 131):
    k0 = sha256(k) if len(k) > 64 else k
    print("   key of %3d bytes -> %3d bytes, then zero padded to 64"
          % (len(k), len(k0)))
print("   Note that two different keys can give the same K0: a key and the same")
print("   key with zeros appended. Use a key of exactly the digest length.")
print()

print("WHY THE NESTING. The obvious construction, H(key || message), is FORGEABLE.")
print("A Merkle and Damgard hash's digest IS its internal state, so an attacker")
print("who has the tag and knows the key's LENGTH can continue the computation.")
SECRET = b"sixteen byte key"
message = b"amount=500&to=8817"
naive = sha256(SECRET + message)
print("   the naive tag H(key || message) =", naive.hex()[:40] + "...")
state = [int.from_bytes(naive[4 * i:4 * i + 4], "big") for i in range(8)]
glue = padding(len(SECRET) + len(message))
extra = b"&to=9999"
forged = sha256(extra, state=state, already=len(SECRET) + len(message) + len(glue))
print("   the attacker appends %r and, WITHOUT the key, computes" % extra.decode())
print("   forged tag                      =", forged.hex()[:40] + "...")
print("   the true tag for that message   =",
      sha256(SECRET + message + glue + extra).hex()[:40] + "...")
print("   identical                       :",
      forged == sha256(SECRET + message + glue + extra))
print("   and the message the server will accept is")
print("      amount=500&to=8817 || padding || &to=9999")
print("   which most parsers read as to=9999.")
print()
print("HMAC is not forgeable this way, because the outer hash is applied to a")
print("FIXED-LENGTH inner digest, so there is no state for an attacker to extend.")
print()
print("and a tag must be compared in CONSTANT TIME:")
print("   a comparison that stops at the first wrong byte leaks how many bytes")
print("   were right, and an attacker who can time it recovers the tag byte by")
print("   byte, needing 256 tries per byte instead of 2 to the power 256.")
print("   Python's hmac.compare_digest exists for this; == does not do it.")
munotes.in298

HMAC

the compact SHA-256 agrees with the standard library: True

RFC 4231's test vectors for HMAC-SHA-256:
   case 1: b0344c61d8db38535ca8afceaf0bf12b...   matches the RFC: True
   case 2: 5bdcc146bf60754e6a042426089575c7...   matches the RFC: True
   case 3: 60e431591ee0b67f0d8a26aacbf5b77f...   matches the RFC: True
   and against the standard library on a fourth input: True

the two pads are constants that differ in half their bits:
   ipad = 0x36 = 00110110
   opad = 0x5c = 01011100
   their XOR   = 01101010, which is 4 bits of 8
   so the inner and outer keys behave as two independent keys from one.

a key longer than the block is HASHED; a shorter one is ZERO PADDED:
   key of   5 bytes ->   5 bytes, then zero padded to 64
   key of  64 bytes ->  64 bytes, then zero padded to 64
   key of 131 bytes ->  32 bytes, then zero padded to 64
   Note that two different keys can give the same K0: a key and the same
   key with zeros appended. Use a key of exactly the digest length.

WHY THE NESTING. The obvious construction, H(key || message), is FORGEABLE.
A Merkle and Damgard hash's digest IS its internal state, so an attacker
who has the tag and knows the key's LENGTH can continue the computation.
   the naive tag H(key || message) = a00f22d1eacfb4313a7fb15d76010065cb56790a...
   the attacker appends '&to=9999' and, WITHOUT the key, computes
   forged tag                      = 68dd1e8ed235322604c51f5cf7ecd5320c3cf2c9...
   the true tag for that message   = 68dd1e8ed235322604c51f5cf7ecd5320c3cf2c9...
   identical                       : True
   and the message the server will accept is
      amount=500&to=8817 || padding || &to=9999
   which most parsers read as to=9999.

HMAC is not forgeable this way, because the outer hash is applied to a
FIXED-LENGTH inner digest, so there is no state for an attacker to extend.

and a tag must be compared in CONSTANT TIME:
   a comparison that stops at the first wrong byte leaks how many bytes
   were right, and an attacker who can time it recovers the tag byte by
   byte, needing 256 tries per byte instead of 2 to the power 256.
   Python's hmac.compare_digest exists for this; == does not do it.
munotes.in299

HMAC

Read six things out of that run.

munotes.in300

HMAC

The compact SHA-256 agrees with the standard library, checked on the first line before anything is built on it.

All three RFC 4231 vectors match, including the 131-byte key, which is longer than the 64-byte block and so must be hashed first. That case is the one implementations get wrong.

The two pads differ in four of eight bits, so K0 XOR ipad and K0 XOR opad differ in half their bits. That is the point of using two constants: the inner and outer computations behave as though they used two independent keys, derived from one.

A key longer than the block is hashed and a shorter one zero-padded, and the run notes the consequence: a key and the same key with zeros appended give the same K0, so they give the same tags. RFC 2104 notes it, and the practical rule is to use a key of exactly the digest length.

And the length-extension forgery succeeds. Given H(secret || message) and the length of the secret, the attacker set the hash's state to the tag, continued the computation with &to=9999, and produced a tag that matches the true tag for the extended message exactly. No key. The message the server sees is amount=500&to=8817, then the padding bytes, then &to=9999, and most parsers take the last value.

munotes.in301

HMAC

HMAC is not forgeable that way, because the outer hash is applied to the inner digest, which is a fixed-length value. There is no state in the output that corresponds to an extendable message, so there is nothing to continue.

Why the nesting works, in the shape an examiner wants

The inner hash produces a fixed-length digest of the key-prefixed message. Whatever length-extension property the hash has applies to that inner computation, and the attacker never sees its output.

The outer hash is applied to a fixed-length input: B bytes of keyed pad plus the inner digest. So the outer computation is a single-block or two-block hash of a fixed size, and length extension is meaningless on it: there is no message length to extend.

And the two keyed pads are effectively two keys. So an attacker cannot use a relation found in the inner computation against the outer one.

One efficiency note worth knowing: K0 XOR ipad and K0 XOR opad are each one block long and depend only on the key, so their compression can be precomputed once and reused for every message. HMAC therefore costs the hash of the message plus about one extra block, not two full hashes.

Two rules about using it

Truncation is allowed and is bounded. FIPS 198-1 permits the output to be truncated to t bytes. A shorter tag is cheaper and weaker: an attacker's chance of guessing a tag is 1 in 2 to the power 8t. The standard requires t to be at least 4 bytes and recommends at least half the digest length.

The comparison must be constant time. A comparison that returns as soon as two bytes differ tells an attacker how many leading bytes were right. With that, a tag is recovered byte by byte, at 256 tries per byte rather than 2 to the power 256 for the whole tag. A 32-byte tag falls in about 8,192 queries instead of never. The run's last lines say so, and the library function for it exists precisely because the obvious comparison is unsafe.

Worked example: where HMAC goes in a protocol

The requirement. A college API accepts amount and to parameters and must reject any request not issued by the college's own system.

The wrong design. tag = SHA256(secret + parameters), tag sent alongside. This is the design the run forges. An attacker who has seen one legitimate request appends &to=9999 and computes a valid tag.

The second wrong design. tag = SHA256(parameters + secret). No length extension, but a collision in SHA-256 gives two parameter strings with the same tag under every key, so the day the hash falls the scheme falls with it, for all keys ever used.

munotes.in302

HMAC

The right design. tag = HMAC-SHA256(secret, canonical_parameters), where canonical_parameters is a form that cannot be parsed two ways: parameters sorted, lengths included, separators that cannot appear in a value. The canonicalisation matters as much as the HMAC, because the forgery above worked partly by making a string that two parsers read differently.

And three more things. Include a timestamp inside the authenticated data and reject stale requests, or replay is still possible. Compare the tag in constant time. And use a key of 32 bytes, exactly the digest length.

The step that carries the marks. Naming all four: HMAC, canonicalisation, a timestamp inside the coverage, and a constant-time comparison. Three of the four are not about the MAC at all, and a scheme that gets only the MAC right is still broken.

Distinctions that carry marks

The first two columns are the two naive constructions: hash the key followed by the message, and hash the message followed by the key. A table cell cannot carry the concatenation symbol inside a code span, so they are named in words.

Key then message, hashed onceMessage then key, hashed onceHMAC
Length extension forgeryyes, performed in this chapternono
A hash collision breaks ityesyes, for every keyno
Has a security proofnonoyes
Specified in a standardnonoFIPS 198-1, RFC 2104
HMACCMAC
Built ona hash functiona block cipher
StandardFIPS 198-1, RFC 2104SP 800-38B
Keys usedtwo derived from one, by ipad and opadtwo derived from one, for the final block
Choose it whena hash is already present, or in softwarea block cipher is already present, as in hardware
ipadopad
Byte0x360x5C
Binary0011011001011100
Used inthe inner hashthe outer hash
They differ infour of eight bits, so the two derived keys differ in half their bits

What beginners get wrong here

Writing H(key || message) as HMAC. That is the construction HMAC exists to replace, and this chapter forges it.

Getting the pads the wrong way round. 0x36 is ipad and goes in the inner hash; 0x5C is opad and goes in the outer one.

Forgetting that a long key is hashed first. A key longer than the block is replaced by its own digest. RFC 4231's third test case checks exactly this.

Comparing tags with ==. It leaks timing and the tag falls byte by byte in a few thousand queries.

Thinking HMAC needs a collision-resistant hash. It does not, which is why HMAC-SHA-1 remains acceptable after SHA-1's collision.

munotes.in303

HMAC

Quick revision

  • HMAC(K, text) = H((K0 XOR opad) || H((K0 XOR ipad) || text)), from FIPS 198-1 and RFC 2104.
  • K0 is the key zero-padded to the block size, or H(K) padded if the key is longer than the block.
  • ipad is 0x36 repeated, for the inner hash; opad is 0x5C, for the outer. They differ in four of eight bits, so the two derived keys differ in half their bits.
  • H(key || message) is forgeable by length extension, performed in this chapter: the tag is the hash's state, so an attacker who knows the key's length extends the message and computes a valid tag without the key.
  • H(message || key) is not extendable but falls to a hash collision, for every key.
  • HMAC's nesting defeats both, and it has a proof: a secure fixed-length compression function gives a secure arbitrary-length MAC.
  • The keyed pads depend only on the key, so their compression is precomputed: HMAC costs one hash plus about a block.
  • Truncation to t bytes is allowed, at least 4 and preferably half the digest; guessing costs 1 in 2 to the power 8t.
  • Compare tags in constant time. A byte-by-byte comparison recovers a 32-byte tag in about 8,192 queries.
  • HMAC does not need collision resistance, which is why HMAC-SHA-1 is still acceptable.

Test yourself

1. Write the HMAC construction and define each part. HMAC(K, text) = H((K0 XOR opad) || H((K0 XOR ipad) || text)). H is a cryptographic hash function with block size B. K0 is the key padded with zeros to B bytes, or H(K) padded to B bytes if the key is longer than B. ipad is the byte 0x36 repeated B times and opad is 0x5C repeated B times.

2. Why is H(key || message) not a secure MAC? Because an iterated hash's digest is its internal chaining state. An attacker who has the tag and knows the length of the key can set the state to that tag and continue the computation with data of their choosing, producing a valid tag for the original message followed by the hash's padding and their own addition, without ever knowing the key. The chapter performs this forgery and the forged tag matches the true one exactly.

3. Why is H(message || key) also unsatisfactory? Because a collision in the hash gives two messages with the same tag under every possible key: if two messages hash identically then appending the same key to both yields identical inputs to the final compression. So a single published collision would break the scheme for all users and all keys at once.

munotes.in304

HMAC

4. What are ipad and opad, and why are there two of them? ipad is the byte 0x36 repeated to the block size and opad is 0x5C repeated likewise. Two are used so that the inner and outer hashes are keyed differently: the two bytes differ in four of their eight bits, so K0 XOR ipad and K0 XOR opad differ in about half their bits and behave as two independent keys derived from one.

5. What happens to a key longer than the hash's block size, and what subtlety follows? It is replaced by its own hash and then zero-padded to the block size. The subtlety is that zero padding means a key and the same key with trailing zero bytes appended produce the same K0, and therefore the same tags, so they are not distinct keys. The practical rule is to use a key of exactly the digest length.

6. How is HMAC made efficient? The two keyed pads, K0 XOR ipad and K0 XOR opad, are each exactly one block long and depend only on the key, so the compression of each can be computed once and reused for every message under that key. HMAC therefore costs the hash of the message plus roughly one extra block, rather than two complete hashes.

7. Why must an HMAC tag be compared in constant time? Because a comparison that stops at the first differing byte takes a time that depends on how many leading bytes matched. An attacker who can measure that recovers the tag one byte at a time, needing about 256 attempts per byte instead of searching the whole tag space, so a 32-byte tag falls in a few thousand queries. A comparison that always examines every byte removes the signal.

Contents This chapter on its own page

munotes.in305

Chapter Fifty

Practical: Writing a Substitution and a Transposition Cipher

Syllabus topic Computer Science Practical 5, Module 2, "Implementing Substitution and Transposition Ciphers"

The exercise, as MU sets it

Practical 5's first exercise reads: "Implementing Substitution and Transposition Ciphers: Design and implement algorithms to encrypt and decrypt messages using classical substitution and transposition techniques."

Three words in that sentence decide what the submission must contain. Algorithms, plural, so both families. Encrypt and decrypt, so the inverse must work, not only the forward direction. And design, so the key handling and the error cases are part of the work rather than an afterthought.

The program, in journal form

# Practical 1: substitution and transposition ciphers.
# Journal form: one program, both ciphers, encryption and decryption, with the
# input validation and the error cases a submission is marked on.

ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

def clean(text):
    """Letters only, upper case. Everything else is discarded."""
    return "".join(c for c in text.upper() if c in ALPHABET)

# ---------- substitution: the Caesar cipher, with a validated key -------------
def caesar(text, key, decrypt=False):
    if not isinstance(key, int) or not 1 <= key <= 25:
        raise ValueError("the Caesar key must be a whole number from 1 to 25")
    k = -key if decrypt else key
    return "".join(ALPHABET[(ALPHABET.index(c) + k) % 26] for c in clean(text))

# ---------- transposition: row transposition, with a validated key -----------
def check_perm(key):
    digits = [int(d) for d in str(key)]
    n = len(digits)
    if sorted(digits) != list(range(1, n + 1)):
        raise ValueError("the key must be a permutation of 1 to %d, not %r" % (n, key))
    return digits

def row_encrypt(text, key, pad="X"):
    order = check_perm(key)
    n = len(order)
    t = clean(text)
    t += pad * (-len(t) % n)
    grid = [t[i:i + n] for i in range(0, len(t), n)]
    out = ""
    for label in range(1, n + 1):
        col = order.index(label)
        out += "".join(row[col] for row in grid)
    return out, grid

def row_decrypt(text, key):
    order = check_perm(key)
    n = len(order)
    t = clean(text)
    if len(t) % n:
        raise ValueError("the ciphertext length %d is not a multiple of %d"
                         % (len(t), n))
    rows = len(t) // n
    cols, at = {}, 0
    for label in range(1, n + 1):
        cols[order.index(label)] = t[at:at + rows]
        at += rows
    return "".join("".join(cols[c][r] for c in range(n)) for r in range(rows))

# ------------------------------- the run --------------------------------------
plain = "Meet me after the toga party"
print("AIM: encrypt and decrypt a message by a substitution cipher and by a")
print("     transposition cipher, and show that each recovers the plaintext.")
print()
print("INPUT")
print("   plaintext        :", plain)
print("   cleaned          :", clean(plain))
print()

print("PART A, SUBSTITUTION: the Caesar cipher, key 3")
c = caesar(plain, 3)
print("   ciphertext       :", c)
print("   decrypted        :", caesar(c, 3, decrypt=True))
print("   recovered        :", caesar(c, 3, decrypt=True) == clean(plain))
print()

print("PART B, TRANSPOSITION: row transposition, key 4312567")
ct, grid = row_encrypt(plain, 4312567)
print("   the grid, with the key across the top:")
print("      4 3 1 2 5 6 7")
for row in grid:
    print("      " + " ".join(row))
print("   read the columns in label order 1 to 7:")
print("   ciphertext       :", ct)
print("   decrypted        :", row_decrypt(ct, 4312567))
print("   recovered (with the padding X's):",
      row_decrypt(ct, 4312567).rstrip("X") == clean(plain))
print()

print("PART C: the letter counts, which identify which cipher was used")
def counts(s):
    return {ch: s.count(ch) for ch in sorted(set(s))}
print("   plaintext counts equal the TRANSPOSITION's counts:",
      counts(clean(plain)) == counts(row_decrypt(ct, 4312567).rstrip("X")))
print("   and differ from the SUBSTITUTION's:",
      counts(clean(plain)) != counts(c))
print()

print("PART D: the error cases, which a submission is marked on")
for bad_call, label in (
        (lambda: caesar(plain, 0), "Caesar key 0"),
        (lambda: caesar(plain, 26), "Caesar key 26"),
        (lambda: caesar(plain, "three"), "Caesar key 'three'"),
        (lambda: row_encrypt(plain, 4312568), "row key 4312568, no 7"),
        (lambda: row_decrypt("ABCDE", 4312567), "ciphertext not a multiple of 7")):
    try:
        bad_call()
        print("   %-32s accepted, WHICH IS A BUG" % label)
    except ValueError as e:
        print("   %-32s rejected: %s" % (label, e))
print()
print("CONCLUSION: both ciphers encrypt and decrypt correctly. The substitution")
print("changes the letters and the transposition only their order, which the")
print("letter counts in Part C demonstrate. Both are broken and neither may be")
print("used to protect anything; they are on the syllabus for the ideas.")
munotes.in306

Practical: Writing a Substitution and a Transposition Cipher

AIM: encrypt and decrypt a message by a substitution cipher and by a
     transposition cipher, and show that each recovers the plaintext.

INPUT
   plaintext        : Meet me after the toga party
   cleaned          : MEETMEAFTERTHETOGAPARTY

PART A, SUBSTITUTION: the Caesar cipher, key 3
   ciphertext       : PHHWPHDIWHUWKHWRJDSDUWB
   decrypted        : MEETMEAFTERTHETOGAPARTY
   recovered        : True

PART B, TRANSPOSITION: row transposition, key 4312567
   the grid, with the key across the top:
      4 3 1 2 5 6 7
      M E E T M E A
      F T E R T H E
      T O G A P A R
      T Y X X X X X
   read the columns in label order 1 to 7:
   ciphertext       : EEGXTRAXETOYMFTTMTPXEHAXAERX
   decrypted        : MEETMEAFTERTHETOGAPARTYXXXXX
   recovered (with the padding X's): True

PART C: the letter counts, which identify which cipher was used
   plaintext counts equal the TRANSPOSITION's counts: True
   and differ from the SUBSTITUTION's: True

PART D: the error cases, which a submission is marked on
   Caesar key 0                     rejected: the Caesar key must be a whole number from 1 to 25
   Caesar key 26                    rejected: the Caesar key must be a whole number from 1 to 25
   Caesar key 'three'               rejected: the Caesar key must be a whole number from 1 to 25
   row key 4312568, no 7            rejected: the key must be a permutation of 1 to 7, not 4312568
   ciphertext not a multiple of 7   rejected: the ciphertext length 5 is not a multiple of 7

CONCLUSION: both ciphers encrypt and decrypt correctly. The substitution
changes the letters and the transposition only their order, which the
letter counts in Part C demonstrate. Both are broken and neither may be
used to protect anything; they are on the syllabus for the ideas.
munotes.in307

Practical: Writing a Substitution and a Transposition Cipher

What each part is for, and what it earns

Part A, the substitution. The Caesar cipher, because it is the cipher the theory chapters introduced first and because it can be checked by eye. clean() strips everything that is not a letter, which is the decision the exercise calls design: a submission that crashes on a space has not designed anything.

Part B, the transposition. Row transposition with the key 4312567, which the theory chapter worked. The grid is printed with the key across the top, because the marking of this exercise turns on whether the columns were read in label order, and printing the grid makes it checkable.

Part C, the identification. The letter counts of the plaintext match the transposition's exactly and differ from the substitution's. That single comparison is the answer to "how would you tell which kind of cipher was used", and it is worth including because the question is asked.

Part D, the error cases. Five bad inputs, all rejected with a message that says what was wrong: a Caesar key of 0, of 26, of the wrong type; a row key that is not a permutation; and a ciphertext whose length is not a multiple of the key length. Note what the program prints if a bad input is accepted: "accepted, WHICH IS A BUG". A test that cannot fail is not a test, and the same rule that governs this book's checkers governs a journal's own tests.

The write-up MU's internal assessment expects

The internal 20 marks are two class tests and two assignments, and an assignment on this module is normally the journal. So the write-up matters as much as the code.

Aim. One sentence. What is being implemented and what will be demonstrated.

Theory, in three or four sentences. A substitution replaces symbols and keeps positions; a transposition keeps symbols and changes positions; the Caesar cipher shifts by a fixed amount modulo 26; row transposition writes in rows of n and reads the columns in the order the key labels them.

Algorithm. Numbered steps for each cipher, in words, before the code.

Program. The listing.

Output. What it printed, copied exactly.

Conclusion. That both ciphers encrypt and decrypt correctly; that the letter counts distinguish them; and, importantly, that both are broken and neither may be used to protect anything. A conclusion that recommends the Caesar cipher has failed the subject rather than the exercise.

munotes.in308

Practical: Writing a Substitution and a Transposition Cipher

Viva questions this exercise attracts, with their answers

"Why 26 in the modulus?" Because the alphabet has 26 letters and the cipher is arithmetic on letter positions, so the arithmetic wraps at 26.

"What if the key is 26?" A shift of 26 is a shift of 0, so the ciphertext equals the plaintext. That is why the program rejects it and why the keyspace is 25 and not 26.

"Why did you pad the last row?" Because the columns must be equal in length for the ciphertext to be read and written back unambiguously. The padding character must be one the receiver can recognise and remove, which is why X is conventional.

"Your decryption returned the padding. Is that a bug?" No, it is what the algorithm returns. Removing it is a separate step that requires knowing the original length or recognising the padding, and a submission should say which it chose.

"How would you break each of these?" The Caesar cipher by trying all 25 keys. The transposition by recognising from the letter counts that it is a transposition, guessing the number of columns, and searching the column orders with digram frequencies as the score.

"Why not use these ciphers?" Because the Caesar keyspace is 25 and a transposition preserves letter frequencies exactly, so both are broken in minutes by hand.

Quick revision

  • MU's exercise wants both families, both directions, and design, which means key validation.
  • Journal form: aim, theory, algorithm, program, output, conclusion.
  • clean() first: letters only, upper case. A program that crashes on a space has not been designed.
  • Print the grid with the key above it, because the marking turns on reading the columns in label order.
  • Letter counts identify the family: identical to the plaintext's means a transposition.
  • Test the error cases, and make the test say so when a bad input is accepted.
  • Reject a Caesar key outside 1 to 25, a row key that is not a permutation, and a ciphertext whose length is not a multiple of the key length.
  • The conclusion must say both ciphers are broken.

Test yourself

1. What does MU's exercise require beyond encryption? Decryption, since it asks for algorithms to encrypt and decrypt; both families, since it names substitution and transposition; and design, which covers validating the key and handling input that is not plain letters.

2. Why must the Caesar key be restricted to 1 to 25? Because a key of 0 or 26 leaves the message unchanged, and any key is equivalent to its remainder modulo 26, so the distinct useful keys are exactly 1 to 25. A program accepting 0 or 26 would report an encryption that had not encrypted.

munotes.in309

Practical: Writing a Substitution and a Transposition Cipher

3. Why is the last row of a row transposition padded? So that every column has the same number of letters. Without that, the receiver cannot tell how many letters belong to each column, and the ciphertext cannot be written back into the grid unambiguously.

4. How do the letter counts distinguish the two ciphers? A transposition only rearranges letters, so the ciphertext's letter counts are exactly the plaintext's. A substitution replaces letters, so its counts are a relabelling and generally differ from the plaintext's. Comparing the counts therefore identifies which family produced a ciphertext.

5. Name three error cases this program must reject. A Caesar key outside the range 1 to 25, or one that is not a whole number; a row transposition key that is not a permutation of 1 to n, such as 4312568, which has no 7; and a ciphertext presented for row decryption whose length is not a multiple of the key length.

6. What should the conclusion of this journal say? That both ciphers encrypt and decrypt correctly and that the letter counts distinguish them; and that both are broken, the Caesar cipher by exhaustive search of 25 keys and the transposition by recognising it from the letter counts and searching the column orders, so neither may be used to protect anything.

7. Your decryption returns MEETMEAFTERTHETOGAPARTYXXXXX. Is the program wrong? No. The trailing X's are the padding the encryption added to fill the last row, and returning them is what the algorithm does. Removing them is a separate decision which requires either knowing the original length or recognising the padding character, and the journal should state which was chosen.

Contents This chapter on its own page

munotes.in310

Chapter Fifty-One

Practical: Generating and Verifying a Message Authentication Code

Syllabus topic Computer Science Practical 5, Module 2, "Message Authentication Codes: Implement algorithms to generate and verify message authentication codes (MACs) for ensuring data integrity and authenticity"

The exercise, as MU sets it

Practical 5's third exercise reads: "Message Authentication Codes: Implement algorithms to generate and verify message authentication codes (MACs) for ensuring data integrity and authenticity."

Generate and verify, so both halves. And for ensuring data integrity and authenticity, which is the claim the journal must support and must not overstate: integrity and authenticity, and not confidentiality, and not non-repudiation.

The program, in journal form

# Practical 2: generating and verifying a message authentication code.

import hashlib
import hmac
import os

def make_tag(key, message):
    """HMAC-SHA-256, as FIPS 198-1 specifies it, via the standard library."""
    return hmac.new(key, message, hashlib.sha256).digest()

def verify(key, message, tag):
    """compare_digest, never ==. See Part D."""
    return hmac.compare_digest(make_tag(key, message), tag)

KEY = bytes(range(32))          # 32 bytes, the digest length, as the rule says
MSG = b"amount=500&to=8817&ts=1759190400"

print("AIM: generate a message authentication code, verify it, and show that a")
print("     tampered message and a wrong key are both rejected.")
print()
print("INPUT")
print("   key    : %d bytes (the digest length, as RFC 2104 recommends)" % len(KEY))
print("   message: %s" % MSG.decode())
print()

tag = make_tag(KEY, MSG)
print("PART A, GENERATE")
print("   tag (32 bytes) :", tag.hex())
print()

print("PART B, VERIFY")
print("   the correct message and tag :", verify(KEY, MSG, tag))
print()

print("PART C, THE THREE FAILURES a submission must show")
tampered = MSG.replace(b"8817", b"9999")
print("   1. the message altered      :", verify(KEY, tampered, tag))
print("      altered message: %s" % tampered.decode())
print("      its own tag    : %s" % make_tag(KEY, tampered).hex()[:32] + "...")
wrong_key = bytes(range(1, 33))
print("   2. the wrong key            :", verify(wrong_key, MSG, tag))
bad_tag = bytearray(tag)
bad_tag[0] ^= 1
print("   3. one bit of the tag flipped:", verify(KEY, MSG, bytes(bad_tag)))
print()

print("PART D, WHY compare_digest AND NOT ==")
print("   a comparison that stops at the first difference tells an attacker how")
print("   many leading bytes were right, so the tag falls byte by byte at about")
print("   256 tries per byte. Counting the work:")
print("      guessing a 32-byte tag whole : 2 to the power 256")
print("      byte by byte with a timing leak: 32 * 256 = %d" % (32 * 256))
print("   compare_digest examines every byte, so the time does not depend on")
print("   where the first difference is.")
print()

print("PART E, THE REPLAY the tag does NOT stop")
print("   the tag is valid on this message for ever, so a captured request can")
print("   be sent again and will verify. The timestamp inside the message is")
print("   what lets the receiver reject it:")
ts = int(MSG.split(b"ts=")[1])
print("      the message's own timestamp : %d" % ts)
print("      a receiver rejects anything older than, say, 300 seconds.")
print("   And the timestamp only helps because it is INSIDE the data the tag")
print("   covers. A timestamp sent beside the tag can be changed freely.")
print()

print("CONCLUSION: HMAC-SHA-256 detects any alteration of the message and any")
print("wrong key. It does not stop replay, which needs a timestamp or a nonce")
print("inside the authenticated data, and it gives no non-repudiation, because")
print("both parties hold the key.")
munotes.in311

Practical: Generating and Verifying a Message Authentication Code

AIM: generate a message authentication code, verify it, and show that a
     tampered message and a wrong key are both rejected.

INPUT
   key    : 32 bytes (the digest length, as RFC 2104 recommends)
   message: amount=500&to=8817&ts=1759190400

PART A, GENERATE
   tag (32 bytes) : a8757a6d89dbda3b27641315b3a5e0d840688d83bd6ace2fb3ee6158d5f0c880

PART B, VERIFY
   the correct message and tag : True

PART C, THE THREE FAILURES a submission must show
   1. the message altered      : False
      altered message: amount=500&to=9999&ts=1759190400
      its own tag    : a55987fa60f6763467f48684df794b62...
   2. the wrong key            : False
   3. one bit of the tag flipped: False

PART D, WHY compare_digest AND NOT ==
   a comparison that stops at the first difference tells an attacker how
   many leading bytes were right, so the tag falls byte by byte at about
   256 tries per byte. Counting the work:
      guessing a 32-byte tag whole : 2 to the power 256
      byte by byte with a timing leak: 32 * 256 = 8192
   compare_digest examines every byte, so the time does not depend on
   where the first difference is.

PART E, THE REPLAY the tag does NOT stop
   the tag is valid on this message for ever, so a captured request can
   be sent again and will verify. The timestamp inside the message is
   what lets the receiver reject it:
      the message's own timestamp : 1759190400
      a receiver rejects anything older than, say, 300 seconds.
   And the timestamp only helps because it is INSIDE the data the tag
   covers. A timestamp sent beside the tag can be changed freely.

CONCLUSION: HMAC-SHA-256 detects any alteration of the message and any
wrong key. It does not stop replay, which needs a timestamp or a nonce
inside the authenticated data, and it gives no non-repudiation, because
both parties hold the key.

What each part is for

Part A, generate. HMAC-SHA-256 through the standard library, because the HMAC chapter showed why the construction is subtle and a practical is not the place to reimplement it. The key is 32 bytes, exactly the digest length, which is RFC 2104's own recommendation.

Part B, verify. One line, returning True.

Part C, the three failures. This is the part that earns the marks. A tag that verifies proves the code runs; a tag that fails on an altered message, on a wrong key, and on a flipped tag bit proves the code works. The altered message's own tag is printed too, so the reader can see that it is entirely different rather than nearly the same.

munotes.in312

Practical: Generating and Verifying a Message Authentication Code

Part D, the comparison. hmac.compare_digest, never ==. The arithmetic is printed: guessing a 32-byte tag whole is 2 to the power 256, and guessing it byte by byte against a comparison that leaks timing is 32 times 256, which is 8,192. That is the difference between impossible and an afternoon.

Part E, replay. The tag is valid on that message for ever, so a captured request replays successfully. The timestamp inside the message is what lets the receiver reject it, and the timestamp works only because it is inside the data the tag covers. A timestamp sent alongside the tag can be changed freely.

The write-up

Aim. Generate and verify a message authentication code, and demonstrate that alteration, a wrong key and a corrupted tag are all detected.

Theory. A MAC is a fixed-length tag computed from the message and a shared secret key. The receiver recomputes it and compares. It gives integrity and data-origin authentication. It gives no confidentiality, because the message is not encrypted, and no non-repudiation, because both parties hold the key.

Algorithm. State HMAC's construction: the key padded to the block size, exclusive-ored with ipad for an inner hash over the message, then with opad for an outer hash over the inner digest.

Program, output. As above.

Conclusion. That alteration and a wrong key are detected; that replay is not prevented without a timestamp or nonce inside the authenticated data; that the tag must be compared in constant time; and that non-repudiation is not available from a shared-key MAC.

Viva questions, with their answers

"Why 32 bytes for the key?" RFC 2104 recommends a key of at least the hash's output length, and a key longer than the block size is hashed first, which gains nothing. 32 bytes is the digest length of SHA-256, so it is the natural choice.

"Could you use MD5?" HMAC-MD5 is not broken in the way MD5 itself is, because HMAC does not rest on collision resistance, but there is no reason to choose it. Use SHA-256.

"Why not just send a hash of the message?" Because a hash has no key, so an attacker who alters the message recomputes the hash. The key is what makes the tag unforgeable.

"Why not H(key || message)?" Because it is forgeable by length extension: the digest is the hash's internal state, so an attacker who knows the key's length appends to the message and computes a valid tag. The HMAC chapter performs that forgery.

"Does this stop somebody sending the same request twice?" No. The tag is valid on that message permanently. A timestamp or a nonce inside the authenticated data, plus a receiver that rejects stale or repeated values, is what stops it.

munotes.in313

Practical: Generating and Verifying a Message Authentication Code

"Can the college prove to the bank that the department sent this?" No. Both hold the key, so either could have produced the tag. That needs a digital signature.

"Why compare_digest?" Because == returns as soon as two bytes differ, so its running time reveals how many leading bytes matched, and an attacker who can time it recovers the tag in about 8,192 queries instead of 2 to the power 256.

Quick revision

  • MU asks for generate and verify, for integrity and authenticity.
  • Use HMAC-SHA-256 from the library; key of 32 bytes, the digest length.
  • Show the three failures: altered message, wrong key, flipped tag bit. A passing test alone proves nothing.
  • Compare with hmac.compare_digest, never ==: a timing leak brings a 32-byte tag from 2 to the power 256 down to 8,192 queries.
  • Replay is not prevented. A timestamp or nonce must be inside the data the tag covers.
  • No non-repudiation: both parties hold the key.
  • No confidentiality: the message travels in clear.

Test yourself

1. What does MU's exercise require, and what must the journal not claim? It requires generating and verifying a message authentication code, for data integrity and authenticity. The journal must not claim confidentiality, since the message is not encrypted, and must not claim non-repudiation, since a shared key means either party could have produced the tag.

2. Why is showing a successful verification insufficient? Because a program that always returns True would pass that test. The demonstration must include failures: an altered message, a wrong key and a corrupted tag must all be rejected, which is what shows the tag is actually being checked.

3. Why must the tag be compared in constant time? Because a comparison that returns at the first differing byte takes a time depending on how many bytes matched, so an attacker who can measure it learns the tag one byte at a time. A 32-byte tag then falls in about 32 times 256, that is 8,192, queries rather than 2 to the power 256.

4. Does a MAC prevent replay? Justify. No. The tag is a function of the message and the key only, so it remains valid however many times the message is sent. Freshness must be added by including a timestamp or a nonce in the data the tag covers, and by having the receiver reject values that are stale or already seen.

5. Why must the timestamp be inside the authenticated data? Because only data covered by the tag is protected. A timestamp sent alongside the tag can be altered by an attacker without invalidating the tag, so the receiver's staleness check could be defeated simply by rewriting the timestamp.

munotes.in314

Practical: Generating and Verifying a Message Authentication Code

6. Why not authenticate with H(key || message)? Because that construction is forgeable by length extension: an iterated hash's digest is its internal state, so an attacker holding the tag and knowing the key's length can continue the computation and produce a valid tag for the message with data appended, without the key.

7. Could HMAC-MD5 be used here? It would not be broken in the way MD5 itself is, because HMAC's security does not depend on the collision resistance of its hash. But there is no advantage to choosing it, and HMAC-SHA-256 should be used.

Contents This chapter on its own page

munotes.in315

Chapter Fifty-Two

Practical: A Diffie-Hellman Exchange in Code

Syllabus topic Computer Science Practical 5, Module 2, "Key Exchange using Diffie-Hellman: Implement the Diffie-Hellman key exchange algorithm to securely exchange keys between two entities over an insecure network"

The exercise, as MU sets it

Practical 5's fifth exercise reads: "Key Exchange using Diffie-Hellman: Implement the Diffie-Hellman key exchange algorithm to securely exchange keys between two entities over an insecure network."

Read the word securely carefully, because it is the trap. The exchange is secure against a party who can read the network and is defeated completely by one who can change what crosses it. A journal that quotes the wording and claims security without qualification will not survive the viva, and the qualification is Part E.

The program, in journal form

# Practical 3: a Diffie-Hellman key exchange, on small numbers and then for real.

import hashlib
import secrets

def order(g, p):
    x, k = g % p, 1
    while x != 1:
        x = (x * g) % p
        k += 1
    return k

print("AIM: perform a Diffie-Hellman key exchange, show that both parties derive")
print("     the same secret, and show that an eavesdropper cannot.")
print()

print("PART A, SMALL NUMBERS, so every value can be checked by hand")
p, g = 353, 3
print("   p = %d, g = %d" % (p, g))
print("   g is a primitive root of p  :", order(g, p) == p - 1)
a, b = 97, 233
A, B = pow(g, a, p), pow(g, b, p)
print("   Alice's secret a = %d, sends A = %d" % (a, A))
print("   Bob's   secret b = %d, sends B = %d" % (b, B))
ka, kb = pow(B, a, p), pow(A, b, p)
print("   Alice computes B^a mod p = %d" % ka)
print("   Bob   computes A^b mod p = %d" % kb)
print("   they agree                 :", ka == kb)
print()

print("PART B, WHAT THE EAVESDROPPER HAS, and what it costs them")
print("   they see p, g, A and B. To get the secret they must find a from A.")
for x in range(1, p):
    if pow(g, x, p) == A:
        print("   brute force found a = %d after %d trials, because p is only %d"
              % (x, x, p))
        break
print("   at 2048 bits there are more trials than atoms in the observable")
print("   universe, and the best known methods are no better than factoring.")
print()

print("PART C, FOR REAL: RFC 3526's 2048-bit group, id 14")
P = int("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
        "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
        "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
        "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
        "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
        "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
        "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
        "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
        "3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFF"
        "FFFFFFFF", 16)
G = 2

def is_prime(n):
    d, r = n - 1, 0
    while d % 2 == 0:
        d //= 2
        r += 1
    for base in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
        x = pow(base, d, n)
        if x in (1, n - 1):
            continue
        for _ in range(r - 1):
            x = (x * x) % n
            if x == n - 1:
                break
        else:
            return False
    return True

print("   the constant is CHECKED before use, not trusted:")
print("      it has %d bits          : %s" % (P.bit_length(), P.bit_length() == 2048))
print("      it is prime             :", is_prime(P))
print("      (p - 1) / 2 is prime    :", is_prime((P - 1) // 2))
print("   so it is a safe prime and g = 2 has order (p - 1) / 2.")
print()
print("   the private exponents come from secrets, not from random:")
alice = secrets.randbelow(P - 2) + 2
bob = secrets.randbelow(P - 2) + 2
Apub, Bpub = pow(G, alice, P), pow(G, bob, P)
sa, sb = pow(Bpub, alice, P), pow(Apub, bob, P)
print("      both private exponents are in range :",
      2 <= alice < P and 2 <= bob < P)
print("      the two are different               :", alice != bob)
print("      both sides derive the same secret   :", sa == sb)
print()

print("PART D, THE SECRET IS HASHED, never used as a key directly")
raw = sa.to_bytes((P.bit_length() + 7) // 8, "big")
key = hashlib.sha256(raw).digest()
print("   the raw secret is %d bytes wide and not uniformly distributed" % len(raw))
print("   SHA-256 of it gives a 32-byte key for AES-256")
print("   and both parties derive the same key:",
      hashlib.sha256(sb.to_bytes(len(raw), "big")).digest() == key)
print()

print("PART E, WHAT THIS PRACTICAL DOES NOT DO")
print("   it agrees a key and does not say WITH WHOM. An attacker who can")
print("   replace both public values ends with one key with each party, and")
print("   neither party can tell. A journal that claims this exchange is secure")
print("   without mentioning authentication has missed the point of it.")
print()
print("CONCLUSION: both parties derive the same secret over a public channel")
print("without having met. The eavesdropper must solve the discrete logarithm")
print("problem, which brute force did here only because p was 353. The exchange")
print("is secure against a PASSIVE attacker and requires authentication of the")
print("public values to be secure against an active one.")
munotes.in316

Practical: A Diffie-Hellman Exchange in Code

AIM: perform a Diffie-Hellman key exchange, show that both parties derive
     the same secret, and show that an eavesdropper cannot.

PART A, SMALL NUMBERS, so every value can be checked by hand
   p = 353, g = 3
   g is a primitive root of p  : True
   Alice's secret a = 97, sends A = 40
   Bob's   secret b = 233, sends B = 248
   Alice computes B^a mod p = 160
   Bob   computes A^b mod p = 160
   they agree                 : True

PART B, WHAT THE EAVESDROPPER HAS, and what it costs them
   they see p, g, A and B. To get the secret they must find a from A.
   brute force found a = 97 after 97 trials, because p is only 353
   at 2048 bits there are more trials than atoms in the observable
   universe, and the best known methods are no better than factoring.

PART C, FOR REAL: RFC 3526's 2048-bit group, id 14
   the constant is CHECKED before use, not trusted:
      it has 2048 bits          : True
      it is prime             : True
      (p - 1) / 2 is prime    : True
   so it is a safe prime and g = 2 has order (p - 1) / 2.

   the private exponents come from secrets, not from random:
      both private exponents are in range : True
      the two are different               : True
      both sides derive the same secret   : True

PART D, THE SECRET IS HASHED, never used as a key directly
   the raw secret is 256 bytes wide and not uniformly distributed
   SHA-256 of it gives a 32-byte key for AES-256
   and both parties derive the same key: True

PART E, WHAT THIS PRACTICAL DOES NOT DO
   it agrees a key and does not say WITH WHOM. An attacker who can
   replace both public values ends with one key with each party, and
   neither party can tell. A journal that claims this exchange is secure
   without mentioning authentication has missed the point of it.

CONCLUSION: both parties derive the same secret over a public channel
without having met. The eavesdropper must solve the discrete logarithm
problem, which brute force did here only because p was 353. The exchange
is secure against a PASSIVE attacker and requires authentication of the
public values to be secure against an active one.
munotes.in317

Practical: A Diffie-Hellman Exchange in Code

What each part is for

Part A, small numbers. p of 353 and g of 3, with g checked to be a primitive root. Both parties derive 160. Every value here can be reproduced on paper, which is what makes the part markable.

Part B, the eavesdropper. Brute force finds Alice's private value after 97 trials. That is the honest way to present the discrete logarithm problem: show it being easy at a size where it is easy, and then say what the size is in practice. A journal that asserts the problem is hard without ever showing it being solved has not demonstrated anything.

Part C, the real group. RFC 3526's 2048-bit MODP group, id 14, with three assertions about the constant before it is used: the bit length, primality, and that (p - 1) / 2 is prime, which makes it a safe prime and guarantees that g of 2 has large order. And the private exponents come from secrets, not from random: random is a pseudorandom generator seeded predictably and is not fit for keys, and naming that distinction is worth a mark.

munotes.in318

Practical: A Diffie-Hellman Exchange in Code

Part D, the key derivation. The shared secret is 256 bytes wide and is not uniformly distributed, so it is hashed to 32 bytes for AES-256. Both parties derive the same key, which the run checks. Using the raw secret as a key is the commonest error in a submission of this exercise.

Part E, the limitation. The exchange agrees a key and does not say with whom. This is the part to write out in full, because it is what the theory chapters established and what the examiner will ask.

The write-up

Aim. Perform a Diffie-Hellman key exchange, show that both parties derive the same secret, show that an eavesdropper must solve the discrete logarithm problem, and state the limitation.

Theory. Public p and a primitive root g. Each party chooses a secret exponent, sends g raised to it modulo p, and raises the received value to its own exponent. Both obtain g to the power ab modulo p. An eavesdropper holding p, g, A and B must solve the discrete logarithm problem.

Algorithm. Five numbered steps, in words.

Program, output. As above.

Conclusion. That both parties derived the same secret over a public channel without having met; that the eavesdropper's task is the discrete logarithm problem, solved here only because p was 353; that the raw secret must be hashed before use as a key; and that the exchange is secure against a passive attacker and requires authentication of the public values against an active one.

Viva questions, with their answers

"Why must g be a primitive root?" Because the shared secret is a power of g, so the number of possible secrets is the order of g. A generator of small order gives a small key space; modulo 11 the generator 10 has order 2 and would allow only two possible keys.

"Which values are secret and which are public?" p and g are public and fixed. A and B are sent in clear and are public. The private exponents a and b never leave their owners, and the derived secret is known only to the two parties.

"Why not use the shared secret as the AES key directly?" Because it is a residue modulo a 2048-bit prime: it is the wrong length, and its bits are not uniformly distributed. A key derivation function, normally hash-based, produces a key of the right size with the right statistical properties.

"Why secrets and not random?" random is a deterministic pseudorandom generator whose state can be recovered from its output, so anything keyed from it is predictable. secrets draws from the operating system's cryptographic source.

"Can an attacker who records the whole exchange recover the key later?" Not from the recording alone, because they would need to solve the discrete logarithm problem. And if the private exponents were discarded after the exchange, the key cannot be recovered even by the participants; that property is called forward secrecy and it is one of the reasons the exchange is used in TLS.

munotes.in319

Practical: A Diffie-Hellman Exchange in Code

"Is this exchange secure?" Against a passive eavesdropper, yes. Against an active attacker who can replace both public values, no: they end with one key with each party and neither party can tell. The exchange must be authenticated, by signing the public values, by certificates, or from a shared password.

"Why did brute force find a so quickly?" Because p was 353, so there were at most 352 exponents to try. At 2048 bits the number of possibilities is beyond any machine, and the best known methods are comparable in cost to factoring a modulus of the same size.

Quick revision

  • MU asks for the exchange over an insecure network, and the qualification is that it resists a passive attacker only.
  • Public: p and a primitive root g. Each party sends g to the power of its own secret; both compute g to the power ab mod p.
  • Small case: p 353, g 3, a 97, b 233 gives both parties 160; brute force found a in 97 trials.
  • Real case: RFC 3526 group 14, 2048 bits, g of 2, with the constant checked: 2048 bits, prime, and (p - 1) / 2 prime, so a safe prime.
  • Private exponents from secrets, never random.
  • The raw secret is hashed to a key; 256 bytes of non-uniform residue is not an AES key.
  • Part E is compulsory: the exchange says nothing about WHO you agreed a key with, and it must be authenticated.
  • Forward secrecy: discard the private exponents and the session key cannot be recovered later, even by the participants.

Test yourself

1. What are the public and private values in a Diffie-Hellman exchange? The modulus p and the generator g are public and fixed; the two transmitted values g to the power a and g to the power b are public; the exponents a and b are private and never transmitted; and the derived secret g to the power ab is known only to the two parties.

2. Why must the generator be a primitive root? Because the shared secret is a power of the generator, so the number of possible secrets equals the generator's order. If the order is a small divisor of p - 1, the key space is correspondingly small and an attacker need try only that many values.

munotes.in320

Practical: A Diffie-Hellman Exchange in Code

3. Why must the shared secret be hashed before use as an encryption key? Because it is a residue modulo a large prime: it is the wrong length for any cipher, and its bits are not uniformly distributed. A key derivation function produces a key of the required length with the statistical properties a cipher expects.

4. Why is secrets used rather than random? Because random is a deterministic pseudorandom generator whose internal state can be reconstructed from a modest amount of its output, so any key derived from it is predictable. secrets draws from the operating system's cryptographic random source, which is designed for this purpose.

5. Brute force recovered the private exponent in 97 trials. Does that show the exchange is insecure? No. It shows the discrete logarithm problem is easy for a 353-element group, which is why such a modulus is never used. At 2048 bits the number of candidates is beyond any machine and the best known methods are comparable in cost to factoring a modulus of the same size.

6. Is this exercise, as MU words it, actually secure over an insecure network? Against an attacker who can only read the network, yes: the exchange reveals nothing about the secret. Against an attacker who can replace what crosses the network, no: substituting both public values gives them one shared key with each party, and neither party can detect it. The exchange must therefore be authenticated, by signing the public values, by certificates, or by deriving them from a shared password.

7. What is forward secrecy, and how does this exchange provide it? That a session key cannot be recovered later even by a party who obtains the long-term keys. A fresh Diffie-Hellman exchange provides it because the session key depends on the two private exponents; if those are discarded after the exchange, the key cannot be reconstructed from anything that was transmitted or from any long-term key.

Contents This chapter on its own page

munotes.in321

Module II

Digital signatures and authentication, authentication applications, electronic mail security, IP security, web security, intrusion, malicious software and firewalls

munotes.in

Chapter Fifty-Three

Digital Signatures: What One Is and What It Must Do

Syllabus topic Module 2, "Digital Signatures and Authentication: Digital Signatures"

In one line

A value that only one person could have produced and that everybody can check. That asymmetry is what a message authentication code cannot give, and it is what makes an electronic document enforceable.

In the wording a student can write in an examination: a digital signature is an authentication mechanism that enables the creator of a message to attach a code that acts as a signature. The signature is formed by taking the hash of the message and encrypting it with the creator's private key. It guarantees the source and the integrity of the message, and because only the holder of the private key could have produced it while anybody holding the public key can verify it, it additionally provides non-repudiation, which no shared-key mechanism can.

What Module 2 is about, in one paragraph

Module 1 built the pieces: ciphers, hashes, keys. Module 2 is what people actually deploy. Its first half is the mechanisms that bind a key to a person, which is signatures, Kerberos, X.509 and the public-key infrastructure. Its second half is the protocols that use them, which is PGP, S/MIME, IPsec and TLS, and then the defences that assume all of that has failed, which is intrusion detection, malicious software and firewalls.

The properties a signature must have

A question asking for "the requirements of a digital signature" wants this list, and there are six.

1. It must depend on the message being signed. Otherwise it could be detached and attached to another message.

2. It must use information unique to the sender, to prevent both forgery and denial.

3. It must be relatively easy to produce.

4. It must be relatively easy to recognise and verify.

5. It must be computationally infeasible to forge, either by constructing a new message for an existing signature, or by constructing a fraudulent signature for a given message. These are two different attacks, and the second is the one the birthday chapter showed being mounted with a collision.

6. It must be practical to retain a copy of the signature in storage.

Requirement 5's two halves map onto the hash properties of Module 1: "a new message for an existing signature" is a second preimage, and "a fraudulent signature for a given message" by the birthday route is a collision. That connection is worth making in an answer.

What a signature gives that a MAC does not

The services chapter proved this and it is worth restating in the form Module 2 uses.

Message authentication codeDigital signature
Verifies the message is unalteredyesyes
Verifies who sent ityes, to the other key holderyes, to anybody
Can the verifier have produced ityesno
Proves authorship to a third partynoyes
Settles a dispute between the partiesnoyes
Costcheapexpensive, so it signs a digest
munotes.in322

Digital Signatures: What One Is and What It Must Do

Rows 3 and 4 are the whole of it. A MAC protects two parties against the world. A signature protects each party against the other, which is what a contract needs.

Two disputes a signature settles

Both are standard examination examples and both are worth having.

The sender denies sending. A department submits marks and later says it did not. With a MAC the University can only say "somebody holding the key sent it", and the department replies "you hold the key too". With a signature the University produces the signature, anybody verifies it with the department's public key, and only the department's private key could have made it.

The receiver forges. The University claims a department submitted marks it never sent. With a MAC the University could simply have computed the tag itself. With a signature it cannot, because it does not hold the department's private key.

And the dispute a signature does not settle: the sender says "my private key was stolen". That is a real defence and it is why key revocation, timestamps and the certificate machinery of the later chapters exist. A signature proves the key was used, not that its owner used it.

What the law says, in India

This matters for our reader, and the Act is remarkably specific.

Section 3, authentication of electronic records. "Any subscriber may authenticate an electronic record by affixing his digital signature", and sub-section (2) says how: "The authentication of the electronic record shall be effected by the use of asymmetric crypto system and hash function which envelop and transform the initial electronic record into another electronic record."

That is hash-then-sign, written into statute. Parliament did not say "use a signature"; it specified an asymmetric cryptosystem and a hash function, which is precisely the arrangement the next chapter implements. The section's own Explanation then defines a hash function, and requires that it be computationally infeasible to derive the record from the hash result, or to find two records with the same hash: preimage and collision resistance, in an Act of Parliament.

Section 3A, electronic signature. A later, technology-neutral addition. A subscriber may authenticate by any electronic signature technique that "is considered reliable" and is specified in the Second Schedule, and sub-section (2) sets out when a technique is reliable: the signature creation data must be "linked to the signatory ... and to no other person" and must have been "under the control of the signatory ... and of no other person" at the time of signing.

munotes.in323

Digital Signatures: What One Is and What It Must Do

Section 5, legal recognition. Where any law requires a signature, "such requirement shall be deemed to have been satisfied" if the matter is authenticated by an electronic signature affixed in the prescribed manner. This is the section that makes an electronic signature a signature in law, and it is the one to cite.

Section 15, secure electronic signature. A signature is secure if the signature creation data was "under the exclusive control of signatory and no other person" and was stored and affixed as prescribed. And the Explanation removes all doubt: "In case of digital signature, the 'signature creation data' means the private key of the subscriber."

One consequence worth stating plainly. The law's test for a secure signature is exclusive control of the private key, so a signature made with a key the subscriber had lent, lost or left on a shared machine may fail that test. The legal risk sits on key custody, not on the mathematics.

Worked example: signing a marks submission, end to end

Step 1: the department prepares the file. A list of student numbers and marks, as bytes.

Step 2: hash it. SHA-256 of the file, 32 bytes. This is what section 3(2)'s "hash function" means.

Step 3: sign the hash. Encrypt the digest with the department's private key. This is section 3(2)'s "asymmetric crypto system", and by section 15's Explanation the private key is the "signature creation data".

Step 4: send the file and the signature. The file is not encrypted, because confidentiality was not required; a signature does not hide anything.

Step 5: the University verifies. Hash the received file, decrypt the signature with the department's public key, compare. If they match, the file is unaltered and was signed by the holder of that private key.

Step 6: where the certificate comes in. The University must be sure that the public key really is the department's, which is the problem the key-distribution chapter set out and which X.509 answers. Steps 1 to 5 are cryptography; step 6 is the whole of the public-key infrastructure.

The step that carries the marks. Step 2 and step 6. A signature scheme signs the digest, not the message, and it is worthless unless the verifier has an authentic public key.

What beginners get wrong here

Saying a signature encrypts the message. It does not. The message travels in clear unless it is separately encrypted. Signing and encrypting are different operations with different keys.

Signing with the receiver's key. You sign with your own private key. Encrypting to somebody uses their public key. Reversing them is the commonest error in the module.

Saying a signature proves the person signed. It proves the private key was used. Whether the owner used it is a question about key custody, which is exactly what section 15 makes the legal test.

munotes.in324

Digital Signatures: What One Is and What It Must Do

Signing the message rather than its hash. The next chapter shows the forgery that follows, and section 3(2) of the Act requires the hash in terms.

Treating a scanned image of a handwritten signature as a digital signature. It is a picture. It depends on no key, verifies nothing, and can be copied from any other document.

Quick revision

  • A digital signature is the hash of the message encrypted with the signer's private key.
  • It gives authentication, integrity and non-repudiation; a MAC gives the first two only.
  • Six requirements: depends on the message; uses information unique to the sender; easy to produce; easy to verify; infeasible to forge, either as a new message for an old signature (second preimage) or a signature for a given message (collision); and practical to store.
  • A MAC protects two parties against the world; a signature protects each against the other.
  • A signature proves the key was used, not that its owner used it.
  • IT Act 2000 section 3(2): authentication "by the use of asymmetric crypto system and hash function", which is hash-then-sign in statute, with the Explanation requiring preimage and collision resistance.
  • Section 3A: a technology-neutral electronic signature, reliable when the creation data is linked to and controlled by the signatory alone.
  • Section 5: where a law requires a signature, an electronic signature satisfies it.
  • Section 15: a signature is secure when the creation data was under the signatory's exclusive control, and the Explanation says the creation data is the private key.

Test yourself

1. Define a digital signature and state the three services it provides. A code attached to a message, formed by taking the hash of the message and encrypting it with the creator's private key. It provides data origin authentication, integrity, and non-repudiation, the last because only the private key holder could have produced it and anybody with the public key can verify it.

2. List the requirements a digital signature must satisfy. It must depend on the message signed; it must use information unique to the sender; it must be relatively easy to produce; it must be relatively easy to recognise and verify; it must be computationally infeasible to forge, both by constructing a new message for an existing signature and by constructing a signature for a given message; and it must be practical to retain a copy of it.

3. Why can a message authentication code not replace a signature? Because the verifier holds the same key as the sender and could therefore have produced the tag. Presented with a message and a valid tag, a third party cannot determine which key holder made it, so authorship cannot be proved against a denial and disputes between the two parties cannot be settled.

munotes.in325

Digital Signatures: What One Is and What It Must Do

4. What does section 3(2) of the Information Technology Act 2000 require, and why is it notable? That authentication of an electronic record be effected by the use of an asymmetric cryptosystem and a hash function which envelop and transform the record into another record. It is notable because it writes the hash-then-sign arrangement into statute, and its Explanation goes on to require that it be computationally infeasible to derive the record from the hash result or to find two records with the same hash, which are preimage and collision resistance.

5. When is an electronic signature "secure" under the Act, and what is the signature creation data? Under section 15, when the signature creation data was, at the time of affixing, under the exclusive control of the signatory and of no other person, and was stored and affixed in the prescribed manner. The Explanation states that in the case of a digital signature the signature creation data means the private key of the subscriber.

6. A signer says their private key was stolen. Does the signature still bind them? The signature proves the key was used; it does not prove the owner used it. Section 15's test of exclusive control is precisely aimed at this, so a key that was lent, lost or left accessible may fail to produce a secure electronic signature. The practical answers are revocation, timestamping and the certificate machinery, and the legal risk sits on key custody rather than on the mathematics.

7. Which key signs and which key verifies? The signer's own private key produces the signature; the signer's public key, which anybody may hold, verifies it. This is the reverse of encryption, where the sender uses the receiver's public key and the receiver uses their own private key.

Contents This chapter on its own page

munotes.in326

Chapter Fifty-Four

The RSA Digital Signature

Syllabus topic Module 2, "Digital Signatures and Authentication: Digital Signatures"

In one line

Sign by raising the hash to the private exponent; verify by raising the signature to the public one and comparing. It is RSA used backwards, and the hash is not optional.

In the wording a student can write in an examination: in the RSA digital signature scheme the signer computes the hash h of the message and forms the signature as

S = h to the power d mod n

using the private key {d, n}. A verifier computes the hash of the received message independently, raises the signature to the public exponent, and accepts if

S to the power e mod n = h

Because e d is congruent to 1 modulo phi(n), raising to d and then to e returns the original value, so a signature made with the private key verifies with the public one and no other value does.

The scheme, run

# Signing with RSA, and the forgery that follows if the HASH is left out.

import hashlib

def egcd(a, b):
    if b == 0:
        return a, 1, 0
    g, x, y = egcd(b, a % b)
    return g, y, x - (a // b) * y

def modinv(a, m):
    g, x, _ = egcd(a % m, m)
    return x % m

# The primes are generated and TESTED, never typed. See the RSA chapters.
import random

def is_prime(n):
    if n < 2:
        return False
    for p in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
        if n % p == 0:
            return n == p
    d, r = n - 1, 0
    while d % 2 == 0:
        d //= 2
        r += 1
    for a in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
        x = pow(a, d, n)
        if x in (1, n - 1):
            continue
        for _ in range(r - 1):
            x = (x * x) % n
            if x == n - 1:
                break
        else:
            return False
    return True

def prime(bits, rng):
    while True:
        c = rng.getrandbits(bits) | (1 << (bits - 1)) | 1
        if is_prime(c):
            return c

rng = random.Random(20260930)
P, Q = prime(512, rng), prime(512, rng)
N, E = P * Q, 65537
D = modinv(E, (P - 1) * (Q - 1))
print("p and q generated and tested:", is_prime(P), is_prime(Q))
print("n has %d bits, e = %d" % (N.bit_length(), E))
print()

def sign(message, d, n):
    """Sign the HASH of the message, never the message itself."""
    h = int.from_bytes(hashlib.sha256(message).digest(), "big")
    return pow(h, d, n)

def verify(message, signature, e, n):
    h = int.from_bytes(hashlib.sha256(message).digest(), "big")
    return pow(signature, e, n) == h

msg = b"Pay Rs 40000 to vendor 8817"
s = sign(msg, D, N)
print("SIGNING")
print("   message   :", msg.decode())
print("   its digest:", hashlib.sha256(msg).hexdigest()[:40] + "...")
print("   signature :", hex(s)[2:42] + "...  (%d bits)" % s.bit_length())
print()
print("VERIFYING")
print("   the real message      :", verify(msg, s, E, N))
altered = b"Pay Rs 40000 to vendor 9999"
print("   one character changed :", verify(altered, s, E, N))
print("   a changed signature   :", verify(msg, s + 1, E, N))
print()

print("WHO CAN DO WHAT. The private key signs, the public key verifies:")
print("   anybody with {e, n} can verify   :", verify(msg, s, E, N))
print("   nobody without d can sign. An attacker guessing a signature:")
guess = 12345
print("      their guess verifies:", verify(msg, guess, E, N))
print()

print("WHY THE HASH. Without it, RSA's malleability forges a signature from two.")
print("Sign two small numbers RAW, with no hash:")
m1, m2 = 3, 5
s1, s2 = pow(m1, D, N), pow(m2, D, N)
forged = s1 * s2 % N
product = m1 * m2
print("   signature on %d and on %d, multiplied together, is a valid" % (m1, m2))
print("   signature on their product %d:" % product)
print("      does it verify:", pow(forged, E, N) == product)
print("   The attacker never used the private key. With the hash in place the")
print("   forgery would have to satisfy H(m1)*H(m2) = H(m1*m2), which no hash does.")
print()

print("AND WHY THE HASH IS NOT OPTIONAL FOR SIZE EITHER:")
long_message = b"x" * 1000
print("   a %d-byte message is %d bits, and the modulus is %d bits"
      % (len(long_message), len(long_message) * 8, N.bit_length()))
print("   so it cannot be signed raw at all. Its digest is 256 bits and can.")
print("   signature on the long message verifies:",
      verify(long_message, sign(long_message, D, N), E, N))
munotes.in327

The RSA Digital Signature

p and q generated and tested: True True
n has 1024 bits, e = 65537

SIGNING
   message   : Pay Rs 40000 to vendor 8817
   its digest: c3443d969d7f4734228d89d02fb8672b99ded729...
   signature : 295fb1b46d67828a7a564bca7f8c1db28fff6fda...  (1022 bits)

VERIFYING
   the real message      : True
   one character changed : False
   a changed signature   : False

WHO CAN DO WHAT. The private key signs, the public key verifies:
   anybody with {e, n} can verify   : True
   nobody without d can sign. An attacker guessing a signature:
      their guess verifies: False

WHY THE HASH. Without it, RSA's malleability forges a signature from two.
Sign two small numbers RAW, with no hash:
   signature on 3 and on 5, multiplied together, is a valid
   signature on their product 15:
      does it verify: True
   The attacker never used the private key. With the hash in place the
   forgery would have to satisfy H(m1)*H(m2) = H(m1*m2), which no hash does.

AND WHY THE HASH IS NOT OPTIONAL FOR SIZE EITHER:
   a 1000-byte message is 8000 bits, and the modulus is 1024 bits
   so it cannot be signed raw at all. Its digest is 256 bits and can.
   signature on the long message verifies: True
munotes.in328

The RSA Digital Signature

Read five things out of that run.

Signing and verifying work, and both failures are shown. A single character changed in the message fails, and a changed signature fails. A submission that shows only the success has demonstrated nothing.

Anybody can verify and nobody can forge. The verification uses {e, n}, which is published. An attacker's guessed signature fails, as it must: they would need the value whose eth power is the digest, which is the RSA problem.

The forgery when the hash is left out. Signing 3 raw gives one value, signing 5 raw gives another, and their product modulo n is a valid signature on 15. The attacker multiplied two signatures they had been given and obtained a third they had not. No private key was used. This is RSA's malleability, which the Module 1 padding chapter demonstrated on encryption, now demonstrated on signatures.

Why the hash stops it. A forged signature on the product would have to be a signature on a message whose digest is the product of the two digests. Since a hash is not multiplicative, no such message can be constructed, and the attack dies.

And the hash is needed for size as well. A 1,000-byte message is 8,000 bits and the modulus is 1,024 bits, so the message is simply too large to be an RSA input. Its 256-bit digest is not.

Signature against encryption, in one table

The two uses of RSA differ in which key does what, and the table is the answer to a question that is set every year.

RSA encryptionRSA signature
The sender usesthe receiver's public keythe sender's private key
The receiver usestheir own private keythe sender's public key
Who can perform the first stepanybodyonly the key owner
Who can perform the secondonly the key owneranybody
Applied tothe message, paddedthe hash of the message
Givesconfidentialityauthentication and non-repudiation
Padding scheme in RFC 8017RSAES-PKCS1-v1_5, RSAES-OAEPRSASSA-PKCS1-v1_5, RSASSA-PSS

The last row matters and is often missed: the padding for signing is a different scheme from the padding for encrypting. Using an encryption padding on a signature, or the reverse, is a real vulnerability. RFC 8017 specifies both pairs separately and recommends PSS for new signature applications.

Both services at once

To sign and encrypt, the order is: sign first, then encrypt.

The sender hashes the message, signs the hash with their own private key, and then encrypts the message together with the signature using the receiver's public key, or more usually using a session key which the receiver's public key carries.

munotes.in329

The RSA Digital Signature

Why that order. If you encrypt first and sign the ciphertext, an attacker who intercepts the message can strip your signature, re-sign the same ciphertext with their own key, and claim to have sent it. Signing the plaintext binds your identity to what the message says, not merely to a block of ciphertext that passed through your hands.

A worked example by hand, on the standard numbers

Every value below was produced by a program before it was written down. An earlier draft did the two exponentiations by hand, reached the right endpoints, and had four of its intermediate reductions wrong. The endpoints agreeing is not a check; each reduction is shown with the multiple of 187 that produces it, so a reader can verify every line.

"With p = 17, q = 11 and e = 7, sign the message whose hash value is 88."

Step 1: the key. n is 187, phi(n) is 160, and d is 23, from the RSA chapter.

Step 2: sign. S is 88 to the power 23 modulo 187. The exponent 23 is 10111 in binary, so five steps.

Bit 1 is 1: square 1 to get 1, then multiply by 88. 88.

Bit 2 is 0: square 88 to get 7,744; 187 times 41 is 7,667, so the remainder is 77.

Bit 3 is 1: square 77 to get 5,929; 187 times 31 is 5,797, giving 132. Multiply by 88 to get 11,616; 187 times 62 is 11,594, giving 22.

Bit 4 is 1: square 22 to get 484; 187 times 2 is 374, giving 110. Multiply by 88 to get 9,680; 187 times 51 is 9,537, giving 143.

Bit 5 is 1: square 143 to get 20,449; 187 times 109 is 20,383, giving 66. Multiply by 88 to get 5,808; 187 times 31 is 5,797, giving 11.

So the signature is 11.

Step 3: verify. 11 to the power 7 modulo 187. The exponent 7 is 111, so three steps.

Bit 1 is 1: square 1, multiply by 11. 11.

Bit 2 is 1: square 11 to get 121, multiply by 11 to get 1,331; 187 times 7 is 1,309, giving 22.

Bit 3 is 1: square 22 to get 484, giving 110 as before; multiply by 11 to get 1,210; 187 times 6 is 1,122, giving 88.

The verification returns 88, which is the hash, so the signature is valid.

The step that carries the marks. Doing step 3 at all. An answer that signs and stops has not shown the scheme works, and the verification is the cheaper calculation because e is small: three steps against five.

munotes.in330

The RSA Digital Signature

What beginners get wrong here

Signing the message instead of its hash. The forgery in this chapter's run is the consequence, and section 3(2) of the IT Act requires the hash.

Using the receiver's key to sign. You sign with your own private key.

Encrypting before signing. Sign first. Otherwise an attacker strips your signature and re-signs the ciphertext.

Using the encryption padding for a signature. RFC 8017 gives separate schemes, and mixing them is a vulnerability.

Thinking verification recovers the message. It recovers the hash, which the verifier compares against the hash they computed themselves. The message must already be in hand.

Quick revision

  • Sign: S = h to the power d mod n, where h is the hash of the message. Verify: S to the power e mod n must equal the hash the verifier computes.
  • Anybody can verify with {e, n}; only the holder of d can sign.
  • Without the hash, signatures multiply: the product of the signatures on 3 and 5 is a valid signature on 15, with no private key. Performed in this chapter.
  • The hash is also needed for size: a 1,000-byte message is 8,000 bits and cannot be an input to a 1,024-bit modulus.
  • Encryption uses the receiver's public key; signing uses the sender's private key.
  • Sign first, then encrypt, or an attacker strips the signature and re-signs the ciphertext.
  • RFC 8017 gives different padding schemes for signing and for encrypting: RSASSA-PSS is recommended for new signatures, RSAES-OAEP for new encryption.
  • Worked: with n of 187 and d of 23, a hash of 88 signs to 11, and 11 to the power 7 modulo 187 returns 88.

Test yourself

1. Give the RSA signing and verification equations. The signature is S = h to the power d modulo n, where h is the hash of the message and {d, n} is the private key. Verification computes S to the power e modulo n with the public key {e, n} and compares the result with the hash of the received message, accepting if they are equal.

2. Why is the hash signed rather than the message? For two reasons. First, security: RSA is multiplicative, so signatures on two raw messages multiply to a valid signature on the product of those messages, as this chapter's run demonstrates on 3 and 5 producing a signature on 15 without the private key; a hash is not multiplicative, so the forgery cannot be constructed. Second, size: a message longer than the modulus cannot be an RSA input at all, whereas its fixed-length digest can.

3. With n = 187, d = 23 and e = 7, sign the hash value 88 and verify the result. The signature is 88 to the power 23 modulo 187, which is 11. Verifying, 11 to the power 7 modulo 187 is 88, which is the hash value, so the signature is valid.

munotes.in331

The RSA Digital Signature

4. Contrast the use of keys in RSA encryption and RSA signing. In encryption the sender applies the receiver's public key and the receiver applies their own private key, so anybody can encrypt and only the owner can read. In signing the sender applies their own private key and any verifier applies the sender's public key, so only the owner can sign and anybody can check.

5. Should a message be signed before or after encryption, and why? Signed first. If the ciphertext is signed, an attacker can strip the signature and re-sign the same ciphertext with their own key, claiming authorship of a message they cannot even read. Signing the plaintext binds the signer to the content rather than to a block that merely passed through their hands.

6. What padding schemes does RFC 8017 specify, and why does the distinction matter? RSAES-PKCS1-v1_5 and RSAES-OAEP for encryption, and RSASSA-PKCS1-v1_5 and RSASSA-PSS for signatures, with OAEP and PSS recommended for new applications. The distinction matters because using an encryption padding for a signature, or the reverse, is a genuine vulnerability rather than an untidiness.

7. Does verification recover the message? No. It recovers the hash value that was signed. The verifier must already hold the message, hash it independently, and compare the two values; if the message were not in hand there would be nothing to authenticate.

Contents This chapter on its own page

munotes.in332

Chapter Fifty-Five

The ElGamal and Schnorr Signature Schemes

Syllabus topic Module 2, "Digital Signatures and Authentication: Digital Signatures"

In one line

ElGamal signs with a fresh random number per message; Schnorr rearranges the same idea so the expensive work happens before the message arrives. Both rest on the discrete logarithm problem, and both die if the random number repeats.

In the wording a student can write in an examination: the ElGamal signature scheme works in the integers modulo a prime q with a primitive root alpha. The private key is X and the public key is Y = alpha to the power X modulo q. To sign a hashed message m the signer chooses a random K coprime to q - 1 and computes

S1 = alpha to the power K mod q

S2 = K inverse times (m - X S1) mod (q - 1)

The signature is the pair (S1, S2), and a verifier accepts if alpha to the power m equals Y to the power S1 times S1 to the power S2, all modulo q. The Schnorr scheme uses a subgroup of prime order q inside the integers modulo a larger prime p, and arranges the computation so that the only exponentiation can be performed before the message is known.

Why a second family at all

RSA signs perfectly well, so a question asking "why ElGamal" deserves an answer with three parts.

A different hard problem. RSA rests on factoring; ElGamal and Schnorr rest on the discrete logarithm. If somebody finds a fast factoring method, a world that uses only RSA loses everything at once. Two families resting on two problems is insurance, and it is why standards specify both.

Shorter signatures. RSA's signature is as long as the modulus, 2,048 bits at current sizes. Schnorr's and DSA's are two numbers each about the size of the subgroup order, which at comparable strength is far shorter. For a smart card or a constrained radio link that difference is decisive.

Faster signing, in the arrangement that matters. Schnorr's expensive step does not depend on the message, so a device can do it while idle and produce a signature almost instantly when one is needed.

And the cost, which a complete answer gives: both need a fresh random value for every signature, and if that value is predictable or repeated the private key is exposed. RSA has no such per-message secret. Section 5 of this chapter shows the damage.

Both schemes, run

# ElGamal and Schnorr signatures, both run on numbers small enough to check.

def egcd(a, b):
    if b == 0:
        return a, 1, 0
    g, x, y = egcd(b, a % b)
    return g, y, x - (a // b) * y

def modinv(a, m):
    g, x, _ = egcd(a % m, m)
    return None if g != 1 else x % m

def order(g, p):
    x, k = g % p, 1
    while x != 1:
        x = (x * g) % p
        k += 1
    return k

# ---------------------------------------------------------------- ElGamal
q, alpha = 19, 10
print("ELGAMAL SIGNATURES, with q = %d and alpha = %d" % (q, alpha))
print("   alpha is a primitive root of q:", order(alpha, q) == q - 1)
x_a = 16                       # the private key
y_a = pow(alpha, x_a, q)       # the public key
print("   private key X = %d, public key Y = alpha^X mod q = %d" % (x_a, y_a))
print()

def elgamal_sign(m, k):
    if egcd(k, q - 1)[0] != 1:
        raise ValueError("k must be coprime to q - 1")
    s1 = pow(alpha, k, q)
    s2 = (modinv(k, q - 1) * (m - x_a * s1)) % (q - 1)
    return s1, s2

def elgamal_verify(m, sig, y):
    s1, s2 = sig
    return pow(alpha, m, q) == (pow(y, s1, q) * pow(s1, s2, q)) % q

m, k = 14, 5
sig = elgamal_sign(m, k)
print("   signing m = %d with a per-message k = %d (coprime to q - 1 = %d):"
      % (m, k, q - 1))
print("      S1 = alpha^k mod q            = %d" % sig[0])
print("      S2 = k inverse (m - X S1) mod (q - 1) = %d" % sig[1])
print("   verification checks alpha^m against Y^S1 times S1^S2, all mod q:")
print("      left  = %d" % pow(alpha, m, q))
print("      right = %d" % (pow(y_a, sig[0], q) * pow(sig[0], sig[1], q) % q))
print("      valid :", elgamal_verify(m, sig, y_a))
print("   a changed message does not verify:", elgamal_verify(m + 1, sig, y_a))
print()

print("   ELGAMAL SIGNATURES ARE NOT UNIQUE: a different k gives a different")
print("   signature on the SAME message, and both verify:")
for kk in (5, 7, 11):
    if egcd(kk, q - 1)[0] != 1:
        continue
    s = elgamal_sign(m, kk)
    print("      k = %2d gives (%d, %d), valid: %s" % (kk, s[0], s[1],
                                                       elgamal_verify(m, s, y_a)))
print()

print("   AND A REUSED k HANDS OVER THE PRIVATE KEY. Two messages, one k:")
m1, m2 = 14, 11
sa, sb = elgamal_sign(m1, 5), elgamal_sign(m2, 5)
print("      signatures (%d, %d) and (%d, %d), with the same S1"
      % (sa[0], sa[1], sb[0], sb[1]))
# From S2 = k^-1 (m - x S1): (S2a - S2b) = k^-1 (m1 - m2) mod (q-1)
diff_s = (sa[1] - sb[1]) % (q - 1)
diff_m = (m1 - m2) % (q - 1)
found = [kk for kk in range(1, q - 1)
         if egcd(kk, q - 1)[0] == 1 and (modinv(kk, q - 1) * diff_m) % (q - 1) == diff_s]
print("      the k values consistent with those two signatures:", found)
print("      and with k known, X is the only value that reproduces the signature:")
for kk in found:
    recovered = [xx for xx in range(q - 1)
                 if (modinv(kk, q - 1) * (m1 - xx * sa[0])) % (q - 1) == sa[1]]
    print("         k = %2d gives X in %s   the true X is %d: %s"
          % (kk, recovered, x_a, x_a in recovered))
print("      So two signatures under one k narrow the private key from 18")
print("      possibilities to a handful, and a third signature finishes it.")
print()

# ---------------------------------------------------------------- Schnorr
print("SCHNORR SIGNATURES. The same family, arranged so that the expensive")
print("part can be done BEFORE the message is known.")
import hashlib
p_s, q_s = 467, 233            # q_s divides p_s - 1
a_s = pow(4, (p_s - 1) // q_s, p_s)
print("   p = %d, q = %d, q divides p - 1: %s" % (p_s, q_s, (p_s - 1) % q_s == 0))
print("   a = %d, and a^q mod p = %d, so a has order q"
      % (a_s, pow(a_s, q_s, p_s)))
s_key = 100
v = modinv(pow(a_s, s_key, p_s), p_s)
print("   private s = %d, public v = a^(-s) mod p = %d" % (s_key, v))
r_k = 77
x = pow(a_s, r_k, p_s)
msg = b"transfer"
e = int(hashlib.sha256(msg + str(x).encode()).hexdigest(), 16) % q_s
y = (r_k + s_key * e) % q_s
print("   signing: pick r = %d, compute x = a^r mod p = %d" % (r_k, x))
print("            e = H(message || x) mod q = %d" % e)
print("            y = (r + s e) mod q = %d" % y)
lhs = (pow(a_s, y, p_s) * pow(v, e, p_s)) % p_s
e2 = int(hashlib.sha256(msg + str(lhs).encode()).hexdigest(), 16) % q_s
print("   verifying: x' = a^y v^e mod p = %d" % lhs)
print("              x' equals x:", lhs == x)
print("              and H(message || x') mod q equals e:", e2 == e)
print()
print("   Note what the signer did BEFORE seeing the message: x = a^r mod p,")
print("   which is the only exponentiation. Once the message arrives only a")
print("   hash and one multiplication remain, which is why Schnorr suits a")
print("   smart card.")
munotes.in333

The ElGamal and Schnorr Signature Schemes

ELGAMAL SIGNATURES, with q = 19 and alpha = 10
   alpha is a primitive root of q: True
   private key X = 16, public key Y = alpha^X mod q = 4

   signing m = 14 with a per-message k = 5 (coprime to q - 1 = 18):
      S1 = alpha^k mod q            = 3
      S2 = k inverse (m - X S1) mod (q - 1) = 4
   verification checks alpha^m against Y^S1 times S1^S2, all mod q:
      left  = 16
      right = 16
      valid : True
   a changed message does not verify: False

   ELGAMAL SIGNATURES ARE NOT UNIQUE: a different k gives a different
   signature on the SAME message, and both verify:
      k =  5 gives (3, 4), valid: True
      k =  7 gives (15, 14), valid: True
      k = 11 gives (14, 12), valid: True

   AND A REUSED k HANDS OVER THE PRIVATE KEY. Two messages, one k:
      signatures (3, 4) and (3, 7), with the same S1
      the k values consistent with those two signatures: [5, 11, 17]
      and with k known, X is the only value that reproduces the signature:
         k =  5 gives X in [4, 10, 16]   the true X is 16: True
         k = 11 gives X in [2, 8, 14]   the true X is 16: False
         k = 17 gives X in [0, 6, 12]   the true X is 16: False
      So two signatures under one k narrow the private key from 18
      possibilities to a handful, and a third signature finishes it.

SCHNORR SIGNATURES. The same family, arranged so that the expensive
part can be done BEFORE the message is known.
   p = 467, q = 233, q divides p - 1: True
   a = 16, and a^q mod p = 1, so a has order q
   private s = 100, public v = a^(-s) mod p = 75
   signing: pick r = 77, compute x = a^r mod p = 361
            e = H(message || x) mod q = 44
            y = (r + s e) mod q = 50
   verifying: x' = a^y v^e mod p = 361
              x' equals x: True
              and H(message || x') mod q equals e: True

   Note what the signer did BEFORE seeing the message: x = a^r mod p,
   which is the only exponentiation. Once the message arrives only a
   hash and one multiplication remain, which is why Schnorr suits a
   smart card.
munotes.in334

The ElGamal and Schnorr Signature Schemes

Read six things out of that run.

munotes.in335

The ElGamal and Schnorr Signature Schemes

ElGamal verifies, and the two sides of the check are printed separately. alpha to the power m is 16, and Y to the power S1 times S1 to the power S2 is also 16. A changed message fails.

ElGamal signatures are not unique. Three different values of K give three different valid signatures on the same message. RSA's signature on a given message is a single fixed value; ElGamal's is one of many. That is worth knowing because it means a signature cannot be used as an identifier of a message, and because it is the property that makes the next point possible.

K must be coprime to q - 1, because the signing equation needs its inverse modulo q - 1. The program refuses a K that is not.

munotes.in336

The ElGamal and Schnorr Signature Schemes

And a reused K gives the private key away. Two signatures share S1, which is the visible signature of the mistake, and the program then searches for every K consistent with the two, finding three, and for each of those every X consistent with the signature, finding three more. Nine candidates out of eighteen, and the true key is among them. On a real 2,048-bit group the same algebra gives the key exactly; here it narrows it, which is what small numbers can show.

Schnorr's group is a subgroup. q of 233 divides p - 1 of 466, and a of 16 satisfies a to the power q congruent to 1 modulo p, so a generates a subgroup of order 233 inside the integers modulo 467. Working in a small subgroup of a large group is what makes the signature short while keeping the discrete logarithm hard.

And Schnorr's order of operations. The signer computes x = a to the power r modulo p before the message is known. When the message arrives, only a hash and one multiplication remain. The verifier's check recomputes x from the signature and confirms the hash. That precomputation is the whole design.

The reused random value, in plain terms

This is the single most important practical fact about this family of schemes, and it has broken real systems.

In ElGamal, S2 = K inverse (m - X S1) mod (q - 1). Two messages signed under the same K share the same S1, so subtracting the two S2 values eliminates X and leaves an equation in K alone. Solve it for K, substitute back, and X follows.

The visible symptom is that the two signatures share their first component. Any observer can see that, without any computation, and it tells them the mistake was made.

The same algebra applies to DSA, which is built on this scheme, and it is how private keys have been recovered from real devices whose random number generators were weak. A signature scheme in this family is only as good as its source of randomness, which is why the Diffie-Hellman practical insisted on a cryptographic random source rather than an ordinary one.

Distinctions that carry marks

RSA signatureElGamal signatureSchnorr signature
Hard problemfactoringdiscrete logarithmdiscrete logarithm, in a subgroup
Signature isone value, modulus-sizeda pair, each about the size of qa pair, each about the size of the subgroup order
Deterministicyesno, a fresh K per messageno, a fresh r per message
Needs a per-message secretnoyesyes
Reusing itnot applicableexposes the private keyexposes the private key
Verification costcheap, e is smalltwo exponentiationstwo exponentiations
Precomputation before the messagenot possiblepartialthe whole exponentiation
Also encryptsyesElGamal encryption exists as a separate schemeno
munotes.in337

The ElGamal and Schnorr Signature Schemes

ElGamalSchnorr
Works inthe whole group modulo qa subgroup of prime order q inside the integers modulo p
Signature sizeabout twice the size of qabout the size of the subgroup order, much smaller
Hash coversthe messagethe message and the commitment x
Design goala signature from the discrete logarithmminimise the work after the message arrives
Descendantthe Digital Signature Algorithmthe Digital Signature Algorithm

What beginners get wrong here

Reusing the random value. It is the one fatal error, it is visible in the signatures, and it hands over the private key.

Choosing K without checking it is coprime to q - 1. The signing equation needs its inverse, which does not otherwise exist.

Thinking two different signatures on one message means something is wrong. In this family a message has many valid signatures, and that is normal.

Confusing ElGamal signing with ElGamal encryption. They are two different schemes that share a name and a setting. The syllabus's "Digital Signatures" topic means the signature scheme.

Saying Schnorr is faster because its arithmetic is cheaper. Its arithmetic is comparable. It is faster when a signature is needed because the exponentiation was already done.

Quick revision

  • ElGamal: private X, public Y = alpha to the power X mod q. Sign with a fresh K coprime to q - 1: S1 = alpha to the power K mod q, and S2 = K inverse times (m - X S1) mod q - 1. Verify that alpha to the power m equals Y to the power S1 times S1 to the power S2, mod q.
  • Signatures are not unique: a different K gives a different valid signature on the same message.
  • A reused K exposes the private key. The two signatures visibly share S1, and the algebra then yields K and hence X.
  • Schnorr works in a prime-order subgroup, so the signature is short, and its only exponentiation x = a to the power r happens before the message is known.
  • Schnorr's hash covers the message and the commitment x.
  • Both rest on the discrete logarithm problem, not on factoring, which is why standards specify two families.
  • Both need a fresh, unpredictable per-message secret; RSA does not.
  • The Digital Signature Algorithm of the next chapter is built from both.

Test yourself

1. Give the ElGamal signing and verification equations. With prime q, primitive root alpha, private key X and public key Y equal to alpha to the power X modulo q: choose K coprime to q - 1, compute S1 as alpha to the power K modulo q and S2 as the inverse of K modulo q - 1 times (m - X S1), reduced modulo q - 1. A verifier accepts if alpha to the power m modulo q equals Y to the power S1 times S1 to the power S2, modulo q.

munotes.in338

The ElGamal and Schnorr Signature Schemes

2. Why must K be coprime to q minus 1? Because the signing equation requires the multiplicative inverse of K modulo q - 1, and that inverse exists only when the two are coprime. A K sharing a factor with q - 1 cannot be used and the signer must choose another.

3. What happens if the same K is used for two messages? The two signatures share the same first component S1, which is visible to anybody. Subtracting the two second components eliminates the private key and leaves an equation in K alone; solving it gives K, and substituting back gives the private key. The chapter's run performs the search and finds the true key among the candidates.

4. Are ElGamal signatures unique? What follows? No. Each choice of K gives a different valid signature on the same message, and the chapter shows three. It follows that a signature cannot be used to identify a message, that two parties signing the same document produce unrelated values, and that a verifier must check the equation rather than compare against an expected signature.

5. What is the essential difference between Schnorr and ElGamal? Schnorr works in a subgroup of prime order q inside the integers modulo a much larger prime p, which makes the signature components small while keeping the discrete logarithm problem hard in the large group; and it arranges the computation so that the single exponentiation, the commitment x equal to a to the power r, is performed before the message is known, leaving only a hash and a multiplication once it arrives.

6. Why is Schnorr's arrangement useful for a smart card? Because the card can compute the commitment while it is idle and store it, so that when a signature is requested it performs only a hash and one modular multiplication. The response is then almost immediate even though the card's processor is slow, and the expensive work was done at a time when nobody was waiting.

7. Why do standards specify signature schemes based on two different hard problems? So that a breakthrough against one does not remove every signature scheme at once. RSA rests on the difficulty of factoring; ElGamal, Schnorr and their descendants rest on the discrete logarithm problem. A fast factoring method would not by itself break the second family, and the reverse also holds.

Contents This chapter on its own page

munotes.in339

Chapter Fifty-Six

Direct and Arbitrated Digital Signatures

Syllabus topic Module 2, "Digital Signatures and Authentication: Digital Signatures"

In one line

A direct signature involves only the two parties; an arbitrated one puts a trusted third party in the middle who sees every signed message before it is delivered. The arbiter exists to solve a problem about time, not about cryptography.

In the wording a student can write in an examination: a direct digital signature involves only the communicating parties: the source signs with its private key, or signs and then encrypts with the destination's public key, and the destination verifies. Its validity depends entirely on the security of the sender's private key. An arbitrated digital signature introduces a trusted third party, the arbiter: every signed message from the sender to the receiver goes first to the arbiter, which subjects it to a number of tests to check its origin and content, then dates it and sends it on with an indication that it has been verified.

The direct signature, and its two weaknesses

The direct form is what the previous three chapters described, and it is what is used in practice. Its weaknesses are worth stating precisely because both are about the passage of time.

Weakness 1: a denial by claiming the key was stolen. At any point a signer can say "my private key was lost or compromised before that date, so the signature is not mine". The verifier cannot disprove it from the signature alone, because a signature proves the key was used, not who used it.

Partial answers, and why they are partial. Require that every compromised key be reported to a central authority immediately: the loss can then be dated, but nothing stops a dishonest signer from reporting a loss that never happened, or from reporting a real loss dishonestly late. Include a timestamp in every signature and require prompt reporting: this narrows the window to the reporting delay. Neither removes the problem; both shrink it, and saying so is a better answer than pretending revocation solves it.

Weakness 2: a key really is stolen, and everything is repudiable. If a private key is genuinely compromised at time T, then every signature made after T is worthless, and the signer can repudiate signatures made before T by claiming the compromise happened earlier. A single key compromise casts doubt backwards as well as forwards, which is why signatures of long-term significance are timestamped by a third party.

And one detail about the direct form that is asked: if the signed message is also encrypted, sign first and encrypt second. If the signature is applied to the ciphertext, then in a dispute the third party must be given the decryption key to check the signature, which exposes the message to them unnecessarily. Signing the plaintext lets a dispute be settled on the signature and the plaintext alone.

munotes.in340

Direct and Arbitrated Digital Signatures

The arbitrated signature: three schemes

All three have the same shape: the sender X sends to the arbiter A, and A forwards to the receiver Y. They differ in what the arbiter can read and in what cryptography is used.

Scheme 1: symmetric encryption, the arbiter sees the message. X computes a hash of the message, forms a signature by encrypting its identity and that hash with the key it shares with A, and sends the message and the signature to A. A decrypts the signature, checks the hash against the message, and forwards everything to Y encrypted under the key A shares with Y, with a timestamp added.

What Y gets. Y cannot verify X's signature directly, because Y does not hold X's key with A. Y must trust A that the message came from X. In a dispute, Y sends the stored signature to A, which can decrypt it and confirm.

Scheme 2: symmetric encryption, the message encrypted to Y. The same arrangement, except that X encrypts the message under a key it shares with Y before signing it. The arbiter now sees a signature over a ciphertext it cannot read, so the message is confidential from A. A still verifies that the signature was made by X and still timestamps.

Scheme 3: public-key, the arbiter reads nothing. X signs the message with its own private key, encrypts the signed bundle with Y's public key, and then signs the whole thing again with its private key before sending it to A. A verifies the outer signature, which proves the message came from X, and cannot read the inner message at all, because it is encrypted to Y. A adds a timestamp, signs, and forwards.

Scheme 3 is the one to remember, because it achieves what the other two do not: the arbiter is trusted to say when a message passed and that it came from X, and is trusted with nothing else. It cannot read the message, cannot forge a message from X, and cannot collude with Y to construct one.

What the arbiter is actually for

Students often think the arbiter verifies the cryptography, which the receiver could do itself in scheme 3. The arbiter's real product is a trusted timestamp.

In scheme 3, Y can verify X's signature without A. What Y cannot do alone is establish when the message was signed, and that is exactly what weakness 1 turns on. A's timestamp is what stops X claiming afterwards that its key had already been compromised: the message is dated by somebody with no interest in the dispute.

munotes.in341

Direct and Arbitrated Digital Signatures

And the price: the arbiter must be trusted, must be available, and sees the traffic pattern of everybody who uses it. A trusted third party is a single point of failure and a single point of surveillance, which is why the direct form plus a separate timestamping service is the modern arrangement rather than a full arbiter.

Distinctions that carry marks

DirectArbitrated
Partiestwothree
Third party sees every messagenoyes, in schemes 1 and 2
Provides a trusted timestampnoyes
Works if the third party is offlineyesno
Used in practiceyes, with separate timestampingrarely as a whole scheme
Scheme 1Scheme 2Scheme 3
Cryptographysymmetricsymmetricpublic-key
Arbiter reads the messageyesnono
Receiver can verify without the arbiternonoyes
Arbiter could forge a message from the senderyesyesno
Arbiter and receiver could collude to forgeyesyesno
Trust required in the arbiterhighhighonly for the timestamp
The weaknessWhat shrinks itWhat does not fix it
Denial by claiming a stolen keytimestamps, prompt reporting of compromisea stronger algorithm
Real compromise casting doubt backwardsthird-party timestamping of important signaturesrevocation alone

What beginners get wrong here

Saying the arbiter checks the mathematics. In scheme 3 the receiver can do that. The arbiter supplies a trusted time.

Saying a direct signature is insecure. It is what everybody uses. Its weaknesses are about the custody of keys over time, not about the signature.

Thinking revocation solves weakness 1. It shrinks the window. A dishonest signer can still claim an earlier compromise than they reported.

Encrypting before signing in the direct form. Sign the plaintext, so that a dispute can be settled without handing the decryption key to a third party.

Forgetting that schemes 1 and 2 let the arbiter forge. The arbiter holds the shared keys and could construct a signature from X. Only the public-key scheme prevents it.

Quick revision

  • Direct: two parties only. Validity depends entirely on the sender's private key remaining secret.
  • Two weaknesses, both about time: a signer can deny by claiming an earlier compromise, and a real compromise casts doubt on signatures made before it as well as after.
  • Timestamps and prompt reporting shrink both; no algorithm removes them.
  • In the direct form, sign first, then encrypt, so a dispute needs no decryption key.
  • Arbitrated: every message passes through a trusted arbiter, which verifies, timestamps and forwards.
  • Scheme 1: symmetric, arbiter reads the message. Scheme 2: symmetric, message encrypted to the receiver, arbiter cannot read it. Scheme 3: public-key, arbiter cannot read the message, cannot forge, and cannot collude with the receiver.
  • The arbiter's real product is a trusted timestamp, not verification.
  • The price is a single point of failure and of surveillance, which is why direct signatures plus a separate timestamping service is the modern arrangement.
munotes.in342

Direct and Arbitrated Digital Signatures

Test yourself

1. Distinguish a direct from an arbitrated digital signature. A direct signature involves only the sender and the receiver: the sender signs with its private key and the receiver verifies with the corresponding public key. An arbitrated signature routes every signed message through a trusted third party, which checks its origin and content, adds a date, and forwards it with an indication that it has been verified.

2. State the two weaknesses of the direct scheme. First, a signer can deny a signature by claiming that their private key had been lost or compromised before it was made, and the signature alone cannot disprove this. Second, if a key really is compromised, every signature made afterwards is void and the signer can also repudiate earlier signatures by claiming the compromise happened sooner, so one loss casts doubt backwards as well as forwards.

3. Do timestamps solve those weaknesses? They shrink them. Including a timestamp in each signature and requiring prompt reporting of any compromise narrows the window in which a dishonest denial is credible to the reporting delay. Neither eliminates the problem, because a signer can still report a loss dishonestly late or claim one that never occurred.

4. Describe the three arbitrated schemes and say what the arbiter can read in each. In the first, using symmetric encryption, the sender signs a hash with the key it shares with the arbiter and sends the message in clear, so the arbiter reads the message. In the second the message is first encrypted under a key shared with the receiver, so the arbiter verifies a signature over a ciphertext it cannot read. In the third, using public-key cryptography, the sender signs the message, encrypts it to the receiver, and signs the result; the arbiter verifies the outer signature and cannot read the message at all.

5. Why is the public-key arbitrated scheme preferred? Because it requires the least trust in the arbiter. The arbiter cannot read the message, cannot construct a message purporting to come from the sender, and cannot collude with the receiver to do so; and the receiver can verify the sender's signature independently. The arbiter is trusted only for the timestamp.

6. What is the arbiter's real function? To supply a trusted date. In the public-key scheme the receiver can verify the signature without the arbiter; what it cannot establish alone is when the message was signed, which is precisely the point on which a later denial turns. The arbiter dates the message as a party with no interest in any dispute.

munotes.in343

Direct and Arbitrated Digital Signatures

7. What is the cost of using an arbiter? The arbiter must be trusted, must be available for any message to pass, and observes who is communicating with whom. It is therefore both a single point of failure and a single point of surveillance, which is why current practice uses direct signatures together with a separate timestamping service rather than a full arbitrated scheme.

Contents This chapter on its own page

munotes.in344

Chapter Fifty-Seven

Authentication Protocols: Mutual Authentication and the Replay Problem

Syllabus topic Module 2, "Digital Signatures and Authentication: Authentication Protocols"

In one line

Two parties must each become convinced that the other is present now, not recorded earlier. Every authentication protocol in this book is a different answer to that, and every failure in one is a failure of freshness.

In the wording a student can write in an examination: an authentication protocol enables communicating parties to satisfy themselves mutually about each other's identity and to exchange session keys. Mutual authentication requires each party to be convinced of the other's identity; one-way authentication requires only the receiver to be convinced of the sender's. The central problem is replay, in which an opponent copies a message and resends it later; the countermeasures are sequence numbers, timestamps and challenge and response with a nonce.

The replay problem, and its forms

A question asking about replay wants the forms named, and there are four.

Simple replay. The opponent copies a message and replays it later.

Repetition that can be logged. A replay within a valid time window, which a receiver keeping records could detect but a stateless one cannot.

Repetition that cannot be detected. The original message was suppressed and did not arrive, so only the replay reaches the receiver, who has nothing to compare it with.

Backward replay without modification. The message is replayed back to the sender. This works whenever the two directions of a conversation are encrypted with the same key and the messages are not distinguishable by direction, and it is the attack that makes a protocol designer include the sender's identity inside every encrypted message.

The three countermeasures, and what each costs

Sequence numbers. Each party keeps the last number seen and rejects anything out of order. Cost: every party must remember the state of every conversation with every other party. For that reason sequence numbers are not generally used for authentication, although they are used inside a session once one has been established.

Timestamps. A message is accepted only if it carries a timestamp close enough to the receiver's clock. Cost: the clocks must be synchronised, and the synchronisation itself becomes a security-critical service. And a subtlety: the window must be wide enough for network delay and narrow enough to be useful, and an attacker who can delay a message can push it outside the window and cause a denial of service, or exploit the window to replay inside it.

Challenge and response. The receiver sends a fresh nonce, a number used once, and the sender must return a function of it. Cost: it needs a round trip before the real message, so it cannot be used for a store-and-forward application such as electronic mail. That limitation is the subject of the next chapter.

munotes.in345

Authentication Protocols: Mutual Authentication and the Replay Problem

The rule that follows and is worth carrying: a connection-oriented protocol can use challenge and response; a connectionless or store-and-forward one must use timestamps, because there is nobody on the other end to challenge.

Needham and Schroeder, and Denning's attack

# The Needham and Schroeder authentication protocol, and Denning's replay.

import hashlib

class Party:
    def __init__(self, name, master):
        self.name = name
        self.master = master

def enc(key, data):
    """Stand-in for a real cipher: a keyed transformation that is reversible."""
    stream = hashlib.sha256(key).digest() * ((len(data) // 32) + 1)
    return bytes(a ^ b for a, b in zip(data, stream))

dec = enc                       # exclusive-or is its own inverse

KA, KB = b"Asha's master key ..............", b"Bharat's master key ..........."
print("THE PROTOCOL AS NEEDHAM AND SCHROEDER SET IT OUT")
print()

# Message 1: Asha -> KDC
n1 = 12345
print("1. Asha to the centre, in clear:")
print("     Asha, Bharat, nonce N1 = %d" % n1)

# Message 2: KDC -> Asha, encrypted under KA
session = b"session key 2026-09-30 aaaaaaaa"
ticket = enc(KB, session + b"|Asha")
reply = enc(KA, session + b"|N1=" + str(n1).encode() + b"|Bharat|TICKET")
print()
print("2. The centre to Asha, ALL encrypted under Asha's master key:")
print("     the session key Ks, the nonce N1 back, Bharat's name, and a")
print("     TICKET for Bharat which is Ks and Asha's name encrypted under")
print("     Bharat's master key, and which Asha cannot read.")
print("     Asha decrypts and finds N1 =", dec(KA, reply).split(b"|N1=")[1].split(b"|")[0].decode())
print("     so the reply is fresh and was not substituted.")

print()
print("3. Asha to Bharat: the ticket, unchanged.")
opened = dec(KB, ticket)
print("     Bharat decrypts it with his own master key and finds")
print("       the session key, and the name", opened.split(b"|")[1].decode())

n2 = 98765
print()
print("4. Bharat to Asha, encrypted under the SESSION key: nonce N2 =", n2)
print("5. Asha to Bharat, encrypted under the session key: f(N2) =", n2 - 1)
print()
print("   Messages 4 and 5 prove Asha is PRESENT and holds Ks. Without them a")
print("   replay of message 3 alone would be accepted.")
print()

print("DENNING'S ATTACK, and why messages 4 and 5 are not enough.")
print()
print("   Suppose an attacker recorded an old run and has since recovered that")
print("   run's session key, by any means: it was short, or it leaked, or the")
print("   cipher of the day was broken. The attacker now:")
print("     replays the OLD message 3, the old ticket, to Bharat;")
print("     receives message 4, a fresh nonce, encrypted under the OLD key;")
print("     they HOLD that key, so they answer message 5 correctly.")
old_session = b"session key 2020-01-01 bbbbbbbb"
old_ticket = enc(KB, old_session + b"|Asha")
attacker_n2 = 55555
challenge = enc(old_session, str(attacker_n2).encode())
answer = enc(old_session, str(attacker_n2 - 1).encode())
print()
print("   the attacker's answer to Bharat's challenge %d is %s"
      % (attacker_n2, dec(old_session, answer).decode()))
print("   which is exactly what Bharat expects:", dec(old_session, answer).decode()
      == str(attacker_n2 - 1))
print()
print("   Bharat now believes he is talking to Asha, using a key the attacker")
print("   holds. NOTHING in messages 3, 4 and 5 carries a time, so Bharat has")
print("   no way to know the ticket is years old.")
print()
print("THE FIX, from Denning: put a TIMESTAMP in the ticket and in message 2,")
print("and have each party reject anything outside a window. Then an old ticket")
print("is recognisable as old. The cost is that every clock must be synchronised,")
print("and a party whose clock is wrong is either locked out or exposed, which")
print("is the trade Kerberos makes and which its own chapters take up.")
munotes.in346

Authentication Protocols: Mutual Authentication and the Replay Problem

THE PROTOCOL AS NEEDHAM AND SCHROEDER SET IT OUT

1. Asha to the centre, in clear:
     Asha, Bharat, nonce N1 = 12345

2. The centre to Asha, ALL encrypted under Asha's master key:
     the session key Ks, the nonce N1 back, Bharat's name, and a
     TICKET for Bharat which is Ks and Asha's name encrypted under
     Bharat's master key, and which Asha cannot read.
     Asha decrypts and finds N1 = 12345
     so the reply is fresh and was not substituted.

3. Asha to Bharat: the ticket, unchanged.
     Bharat decrypts it with his own master key and finds
       the session key, and the name Asha

4. Bharat to Asha, encrypted under the SESSION key: nonce N2 = 98765
5. Asha to Bharat, encrypted under the session key: f(N2) = 98764

   Messages 4 and 5 prove Asha is PRESENT and holds Ks. Without them a
   replay of message 3 alone would be accepted.

DENNING'S ATTACK, and why messages 4 and 5 are not enough.

   Suppose an attacker recorded an old run and has since recovered that
   run's session key, by any means: it was short, or it leaked, or the
   cipher of the day was broken. The attacker now:
     replays the OLD message 3, the old ticket, to Bharat;
     receives message 4, a fresh nonce, encrypted under the OLD key;
     they HOLD that key, so they answer message 5 correctly.

   the attacker's answer to Bharat's challenge 55555 is 55554
   which is exactly what Bharat expects: True

   Bharat now believes he is talking to Asha, using a key the attacker
   holds. NOTHING in messages 3, 4 and 5 carries a time, so Bharat has
   no way to know the ticket is years old.

THE FIX, from Denning: put a TIMESTAMP in the ticket and in message 2,
and have each party reject anything outside a window. Then an old ticket
is recognisable as old. The cost is that every clock must be synchronised,
and a party whose clock is wrong is either locked out or exposed, which
is the trade Kerberos makes and which its own chapters take up.
munotes.in347

Authentication Protocols: Mutual Authentication and the Replay Problem

Read five things out of that run.

Message 2 is the interesting one. It contains the session key, the nonce back, so Asha knows the reply is fresh; the original request, so she knows it was not altered in flight; and the ticket, which is the same session key and Asha's name encrypted under Bharat's master key. Asha cannot read the ticket and cannot alter it, which is exactly the property wanted.

Messages 4 and 5 exist to prove liveness. Bharat sends a fresh nonce under the session key and Asha returns a function of it, conventionally one less. An attacker replaying message 3 alone could not answer.

But an attacker who holds the old session key can. The run does it: the old ticket is replayed, Bharat's fresh challenge arrives encrypted under the old key, and the attacker, holding that key, returns the expected value. The program prints it and checks it. Bharat is now talking to an attacker and believes it is Asha.

Nothing in messages 3, 4 and 5 carries a time. Bharat has no way to know the ticket is years old. The protocol has a freshness check and is still replayable, because the freshness check proves only that somebody holding the key is present now, and the attacker is.

The fix is a timestamp, in the ticket and in message 2, with each party rejecting anything outside a window. That is Denning's proposal and it is what Kerberos adopts, at the price of synchronised clocks.

What the attacker needed, and what they did not

Worth stating because students overestimate the attack.

They needed a recording of an old run, and the old session key. The key might have been recovered because it was short, because the cipher of that era was later broken, because it was stored somewhere, or simply because the session ended and nobody thought a spent key mattered.

They did not need Asha's or Bharat's master key, any cryptanalysis of the current cipher, or any access to the key distribution centre.

That is the shape of most real protocol attacks: not breaking the cryptography, but using a piece of it after it was assumed to be worthless. A session key that is "finished" is still a live secret as long as any protocol will accept a message encrypted under it.

Distinctions that carry marks

Sequence numbersTimestampsChallenge and response
Needsstate per pair of partiessynchronised clocksa round trip
Suitstraffic inside an established sessionconnectionless and store-and-forwardconnection-oriented
Works for electronic mailnoyesno
Fails whenstate is losta clock drifts, or a message is delayedthere is nobody to challenge
Used inTCP sequence numbers, IPsec anti-replayKerberosTLS handshake, IKE
munotes.in348

Authentication Protocols: Mutual Authentication and the Replay Problem

Mutual authenticationOne-way authentication
Convincedboth partiesthe receiver only
Needs the other party presentyesno
Typical settinga connectionelectronic mail
Freshness bychallenge and responsetimestamp
The replayWhat it is
Simple replaya copied message resent later
Logged repetitiona replay inside the valid window
Undetectable repetitionthe original was suppressed, so only the replay arrives
Backward replaythe message is sent back to its own sender

What beginners get wrong here

Saying a nonce stops replay. It stops the replay of a message that does not contain the nonce. It does not stop an attacker who can answer the challenge, which is exactly what Denning's attack does.

Thinking the attack breaks the cipher. It breaks nothing. It uses an old session key that the protocol is still willing to accept.

Forgetting why messages 4 and 5 are there. They prove the requester is live. An answer that lists five messages without saying what the last two are for has described the protocol without explaining it.

Saying timestamps solve replay. They solve it at the cost of synchronised clocks, and the clock service then becomes security critical. That trade is Kerberos's, and it is worth naming as a trade.

Using sequence numbers for authentication. They require each party to remember state for every other party, which does not scale, which is why they are used within a session and not to establish one.

Quick revision

  • Mutual authentication convinces both parties; one-way convinces the receiver only.
  • The central problem is replay. Four forms: simple, logged repetition, undetectable repetition when the original was suppressed, and backward replay to the sender.
  • Three countermeasures: sequence numbers (state per pair, so not used to establish a session), timestamps (need synchronised clocks), challenge and response with a nonce (needs a round trip, so no good for electronic mail).
  • Needham and Schroeder, five messages: request with a nonce; the centre's reply under the requester's master key carrying the session key, the nonce, the request and a ticket; the ticket forwarded; a nonce challenge under the session key; and the response.
  • Messages 4 and 5 exist to prove the requester is live.
  • Denning's attack: an attacker with an old recording and the old session key replays the ticket, receives the challenge under that key, and answers it. Nothing in the protocol carries a time, so the receiver cannot tell.
  • The attacker needs no master key and no cryptanalysis; only an expired session key the protocol will still accept.
  • The fix is a timestamp in the ticket, which is what Kerberos does.
munotes.in349

Authentication Protocols: Mutual Authentication and the Replay Problem

Test yourself

1. What is the central problem an authentication protocol must solve? Replay: an opponent copying a valid message and resending it later, so that the receiver is convinced of the presence of a party who is not actually there. Every countermeasure in the topic is a way of establishing that a message is fresh.

2. Name the four forms of replay. Simple replay, in which a copied message is resent later; repetition that can be logged, a replay within the valid time window; repetition that cannot be detected, where the original message was suppressed so only the replay arrives; and backward replay without modification, in which the message is replayed back to its own sender.

3. Give the three countermeasures and the cost of each. Sequence numbers, which require each party to keep state for every other party and so are not used to establish a session. Timestamps, which require the parties' clocks to be synchronised, making the clock service security critical. Challenge and response with a nonce, which requires a round trip and therefore cannot be used for store-and-forward applications such as electronic mail.

4. Describe the five messages of the Needham and Schroeder protocol and say what messages 4 and 5 achieve. The requester asks the centre for a key to talk to a named party, including a nonce. The centre replies, under the requester's master key, with the session key, the nonce, the original request and a ticket containing the session key and the requester's name encrypted under the other party's master key. The requester forwards the ticket. The other party sends a nonce encrypted under the session key. The requester returns a function of that nonce. Messages 4 and 5 prove that the requester is present now and holds the session key, rather than being a replay of message 3 alone.

5. Describe Denning's attack and say why messages 4 and 5 do not prevent it. An attacker who has recorded an old run and has since obtained that run's session key replays the old ticket. The receiving party, having no way to tell the ticket's age, accepts it and issues a fresh nonce encrypted under that old session key. Because the attacker holds the key, they decrypt the nonce and return the correct response. Messages 4 and 5 prove only that somebody holding the key is present, and the attacker is; they say nothing about the key being current.

6. What did the attacker need, and what did they not need? They needed a recording of an old exchange and the session key from it, which might have been recovered because it was short, because the cipher was later broken, or because it was stored and forgotten. They needed no master key, no access to the key distribution centre and no cryptanalysis of the cipher in current use.

munotes.in350

Authentication Protocols: Mutual Authentication and the Replay Problem

7. What is the fix, and what does it cost? Include a timestamp in the ticket and in the centre's reply, and have each party reject anything outside an acceptable window, so that an old ticket is recognisable as old. The cost is that every party's clock must be synchronised and kept so, which makes the time service itself security critical; a party whose clock is wrong is either locked out or exposed. Kerberos makes exactly this trade.

Contents This chapter on its own page

munotes.in351

Chapter Fifty-Eight

One-Way Authentication, and Authentication with a Public Key

Syllabus topic Module 2, "Digital Signatures and Authentication: Authentication Protocols"

In one line

When the other party is not there, you cannot challenge them, so the message must carry its own proof of freshness. That proof is a timestamp, and a timestamp needs a clock everybody agrees about.

In the wording a student can write in an examination: one-way authentication is required where the sender and the recipient are not simultaneously present, as in electronic mail. The recipient must be satisfied that the message originated with the claimed sender and has not been altered or replayed, but no interaction with the sender is possible. Challenge and response is therefore unavailable, and freshness must come from a timestamp carried in the message itself.

Why electronic mail forces the issue

Two properties of electronic mail make it the standard example, and both should be named.

The recipient is not online. A message is stored and delivered later, so the sender cannot be challenged and the recipient cannot ask a question. Any protocol requiring a round trip before the message is unusable.

The mail system must handle the message without reading it. Relays, spam filters and mailboxes touch the message, so it cannot depend on a live encrypted channel and must be self-contained. Everything needed to verify it must travel with it.

Those two properties are why PGP and S/MIME look as they do, and this chapter is the reason those chapters exist in the form they take.

The symmetric approach, and its limit

Using a key distribution centre, the sender obtains a session key and a ticket as before, and sends the recipient the ticket together with the message encrypted under the session key.

What works. The recipient can decrypt the ticket with its master key, recover the session key, and read the message. The centre vouches for the sender's identity.

What does not. There is no way for the recipient to challenge, so the whole exchange rests on the ticket being fresh, which means a timestamp, which means the suppress-replay problem of section 5. And the centre must be online when the sender wants to send, which is a weaker requirement than both parties being online but is still a requirement.

And a third thing, which is the honest objection. The centre knows every master key and therefore can read every message. For mail between two people that is a great deal of trust to place in an institution.

The public-key approach, in three arrangements

Each arrangement gives a different set of services, and the difference is which key is applied and in which order.

Arrangement 1: confidentiality only. The sender encrypts a session key with the recipient's public key and the message with the session key. Anybody could have sent it, because the recipient's public key is public. Suitable only when the recipient does not care who wrote it.

munotes.in352

One-Way Authentication, and Authentication with a Public Key

Arrangement 2: authentication only. The sender hashes the message and signs the hash with their own private key, sending the message in clear with the signature. Anybody can verify. The message is readable by anybody, which for a public announcement is correct and for a private letter is not.

Arrangement 3: both. The sender signs the hash with their private key, then encrypts the message and the signature with a session key, and encrypts the session key with the recipient's public key. This is what PGP and S/MIME actually do, and the order is the one the RSA signature chapter argued for: sign the plaintext, then encrypt.

And in every arrangement the recipient must already hold an authentic copy of the sender's public key, which is the certificate problem. The mail protocols solve it in two different ways, PGP with a web of trust and S/MIME with X.509 certificates, and those are their own chapters.

The suppress-replay problem

This is the part worth the chapter.

Once a protocol relies on timestamps, three things follow, and they are design constraints rather than details.

The clocks must be synchronised. Every party must agree about the time to within the acceptance window, which is typically a few minutes. So the time service becomes part of the trusted computing base. If an attacker can influence a party's clock, they can influence what that party will accept.

The window is a compromise that cannot be won. Too narrow and legitimate messages delayed by the network are rejected, which is a denial of service. Too wide and an attacker has that long to replay a captured message. There is no width that removes both problems, only a choice about which to suffer.

And the suppress-replay attack. Suppose a sender's clock runs ahead of the recipient's. The sender issues a message timestamped in the recipient's future. An attacker suppresses it, holds it, and delivers it later when the recipient's clock has caught up. The message is accepted as current although it is old, and the sender never saw it arrive. The attack needs no cryptography at all, only the ability to delay a message and a small clock discrepancy.

The answers, and their costs. Have each party rely on its own clock rather than a shared one, and use a handshake to establish a shared notion of time, which reintroduces the round trip. Or keep a record of recently seen messages and reject duplicates, which reintroduces state. Or, in practice, do both and accept a small residual risk. There is no arrangement that gives freshness without either a round trip or shared state, and that sentence is the conclusion of the whole topic.

munotes.in353

One-Way Authentication, and Authentication with a Public Key

Worked example: choosing the arrangement

Case 1: a college emails a signed examination timetable to every department. Arrangement 2. It is a public document, so there is nothing to hide; it must be provably from the college; the departments are not online; and every department can verify it independently with the college's public key.

Case 2: a department emails a marks file to the Examination Section. Arrangement 3. It must be confidential and provably from the department, and the Section may need to prove authorship later. Sign the hash, encrypt the file and the signature under a session key, encrypt that key to the Section.

Case 3: a student's browser talks to the college portal. Neither. Both parties are online, so mutual authentication with challenge and response is available and is better, because it needs no synchronised clocks. This is the TLS handshake.

Case 4: a sensor reports a reading every second to a collector. Timestamps plus a sequence number inside the authenticated data. There is no round trip to spare and the collector can keep the sequence state for a small number of sensors.

The step that carries the marks. Case 3. If both parties are present, do not use a timestamp. A challenge is stronger and costs nothing but a round trip you were going to spend anyway.

Distinctions that carry marks

Mutual authenticationOne-way authentication
Both parties onlineyesno
Freshness bychallenge and responsetimestamp
Needs synchronised clocksnoyes
Round trip before the messageyesno
Examplethe TLS handshake, KerberosPGP, S/MIME
ArrangementConfidentialityAuthenticationSignatureUsed by
Session key under the recipient's public keyyesnonoencryption-only mail
Sign the hash with the sender's private keynoyesyesa public signed announcement
Sign, then encrypt to the recipientyesyesyesPGP and S/MIME
The suppress-replay attack
Needsthe ability to delay a message, and a clock discrepancy
Needs nocryptanalysis, key or access to either party
Answered bya handshake, or stored state, or both, with a residual risk

What beginners get wrong here

Using challenge and response for electronic mail. There is nobody to challenge.

Treating synchronised clocks as free. The time service becomes security critical, and an attacker who can shift a clock can shift what a party accepts.

Thinking a wider window is safer. It is safer against network delay and worse against replay. The choice is which failure to have.

Encrypting to the recipient and calling it authenticated. The recipient's public key is public; anybody could have sent it.

Using timestamps when both parties are online. A challenge is available and needs no clock agreement.

munotes.in354

One-Way Authentication, and Authentication with a Public Key

Quick revision

  • One-way authentication is needed when the parties are not simultaneously present, above all for electronic mail.
  • Challenge and response is unavailable, so freshness comes from a timestamp in the message.
  • The message must be self-contained: everything needed to verify it travels with it.
  • Three public-key arrangements: encrypt to the recipient (confidentiality only, anybody could have sent it); sign the hash (authentication and signature, no confidentiality); sign then encrypt (all three, and what PGP and S/MIME do).
  • Timestamps make the clock a security-critical service, and the acceptance window is a compromise with no winning width.
  • The suppress-replay attack: with a small clock discrepancy, an attacker delays a message timestamped in the recipient's future and delivers it when the clock has caught up. No cryptography is involved.
  • The answers are a handshake, stored state, or both. There is no freshness without a round trip or shared state.
  • If both parties are online, use a challenge, not a timestamp.

Test yourself

1. What is one-way authentication and when is it required? Authentication in which only the recipient must be satisfied of the sender's identity and of the message's integrity, with no interaction between the parties. It is required where the two are not simultaneously present, the standard case being electronic mail, in which the message is stored and delivered later.

2. Why can challenge and response not be used for electronic mail? Because it requires a round trip: the recipient must send a nonce and the sender must answer it before the real message is accepted. In a store-and-forward system the sender is no longer present when the message is delivered, so there is nobody to challenge and nobody to answer.

3. Give the three public-key arrangements for one-way authentication and what each provides. Encrypting a session key with the recipient's public key and the message under that session key gives confidentiality only, since anybody may use a public key. Signing the hash of the message with the sender's private key gives authentication and a digital signature but no confidentiality. Signing the hash and then encrypting the message and signature to the recipient gives all three, and is what PGP and S/MIME do.

4. What are the costs of relying on timestamps? Every party's clock must be synchronised to within the acceptance window, so the time service becomes part of what must be trusted and an attacker who can influence a clock can influence what a party accepts. And the window itself is a compromise: too narrow rejects messages delayed by the network, too wide gives an attacker longer to replay, and no width removes both problems.

munotes.in355

One-Way Authentication, and Authentication with a Public Key

5. Describe the suppress-replay attack. If a sender's clock is ahead of the recipient's, a message may carry a timestamp lying in the recipient's future. An attacker intercepts and suppresses that message, holds it, and delivers it once the recipient's clock has advanced. The recipient then accepts an old message as current, and the sender, having seen no acknowledgement, may not realise. The attack requires only the ability to delay a message and a small clock discrepancy, and no cryptography at all.

6. What are the answers to it, and what do they cost? Each party can rely on its own clock and establish a shared notion of time by a handshake, which reintroduces the round trip that one-way authentication was meant to avoid; or each can keep a record of recently seen messages and reject duplicates, which reintroduces per-party state. In practice both are used with a residual risk accepted. There is no arrangement providing freshness without either a round trip or stored state.

7. Both parties in a conversation are online. Should timestamps be used for freshness? No. A challenge and response is available and is stronger, because it establishes freshness from a value the verifier chose at that moment and requires no agreement about the time. The round trip it costs is one the parties were already spending on establishing the connection.

Contents This chapter on its own page

munotes.in356

Chapter Fifty-Nine

The Digital Signature Standard, and the DSA Algorithm

Syllabus topic Module 2, "Digital Signatures and Authentication: Digital Signature Standard"

In one line

A signature scheme built on Schnorr and ElGamal, producing two short numbers instead of one long one. It signs and cannot encrypt, and it dies if the per-message secret repeats.

In the wording a student can write in an examination: the Digital Signature Standard is FIPS PUB 186, which specified the Digital Signature Algorithm, DSA, based on the ElGamal and Schnorr schemes and using the SHA family for hashing. Three parameters are public and may be shared by a community of users: a prime p, a prime divisor q of p - 1, and g of order q modulo p. A user's private key x is a random number less than q, and the public key is y = g to the power x modulo p. To sign, the user chooses a per-message secret k less than q and computes

r = (g to the power k mod p) mod q

s = k inverse times (H(M) + x r) mod q

The signature is the pair (r, s).

The DSS approach against RSA

MU's topic is "Digital Signature Standard", and the first thing a question wants is how the standard's approach differs from the RSA one.

In the RSA approach the message is hashed, and the hash is encrypted with the sender's private key. The signature is that encryption, and verification decrypts it with the public key and compares against the hash. The signature is a single value the size of the modulus.

In the DSS approach the hash and a per-message random number are fed into a signature function along with the private key and the public global parameters, and the function produces two values. Verification feeds the hash, the two signature values and the sender's public key into a verification function, which produces a value compared against r. Nothing is decrypted, and nothing is recovered; the verifier recomputes.

The structural difference worth stating: RSA's verification recovers the hash; DSA's does not. DSA's verifier produces a value and compares it with r, which means DSA can never be used to recover a message and therefore cannot encrypt at all. RSA can do both, which is why RSA is the more general algorithm and DSA the more specialised one.

The algorithm, run against published values

# The Digital Signature Algorithm, run on the parameters FIPS 186-2 Appendix 5
# publishes, so every value can be checked against an outside source.

import hashlib

def egcd(a, b):
    if b == 0:
        return a, 1, 0
    g, x, y = egcd(b, a % b)
    return g, y, x - (a // b) * y

def modinv(a, m):
    g, x, _ = egcd(a % m, m)
    return x % m

p = 0x8df2a494492276aa3d25759bb06869cbeac0d83afb8d0cf7cbb8324f0d7882e5
p = (p << 256) | 0xd0762fc5b7210eafc2e9adac32ab7aac49693dfbf83724c2ec0736ee31c80291
q = 0xc773218c737ec8ee993b4f2ded30f48edace915f
g = 0x626d027839ea0a13413163a55b4cb500299d5522956cefcb3bff10f399ce2c2e
g = (g << 256) | 0x71cb9de5fa24babf58e5b79521925c9cc42e9f6f464b088cc572af53e6d78802
x = 0x2070b3223dba372fde1c0ffc7b2e3b498b260614          # the private key
y = pow(g, x, p)                                        # the public key

print("THE PARAMETERS, checked before use:")
print("   p has %d bits, q has %d bits" % (p.bit_length(), q.bit_length()))
print("   q divides p - 1                :", (p - 1) % q == 0)
print("   g has order q, that is g^q = 1 :", pow(g, q, p) == 1)
print("   g is not 1                     :", g != 1)
print()
print("   p, q and g are PUBLIC and may be shared by a whole community.")
print("   x is the private key, y = g^x mod p is the public key.")
print("   y begins %s..." % hex(y)[2:26])
print()

def H(message):
    """FIPS 186-4 signs the leftmost bits of the hash, as many as q has."""
    d = hashlib.sha1(message).digest()
    z = int.from_bytes(d, "big")
    return z >> max(0, len(d) * 8 - q.bit_length())

def sign(message, k):
    r = pow(g, k, p) % q
    if r == 0:
        raise ValueError("r came out 0; choose another k")
    s = (modinv(k, q) * (H(message) + x * r)) % q
    if s == 0:
        raise ValueError("s came out 0; choose another k")
    return r, s

def verify(message, sig, y):
    r, s = sig
    if not (0 < r < q and 0 < s < q):
        return False
    w = modinv(s, q)
    u1 = (H(message) * w) % q
    u2 = (r * w) % q
    v = ((pow(g, u1, p) * pow(y, u2, p)) % p) % q
    return v == r

msg = b"abc"
k = 0x358dad571462710f50e254cf1a376b2bdeaadfbf
r, s = sign(msg, k)
print("SIGNING the message 'abc' with the published per-message secret k:")
print("   r = g^k mod p, then mod q      = %x" % r)
print("   s = k inverse (H(M) + x r) mod q = %x" % s)
print("   the published values for these parameters are")
print("     r = 8bac1ab66410435cb7181f95b16ab97c92b341c0")
print("     s = 41e2345f1f56df2458f426d155b4ba2db6dcd8c8")
print("   they agree:", "%x" % r == "8bac1ab66410435cb7181f95b16ab97c92b341c0"
      and "%x" % s == "41e2345f1f56df2458f426d155b4ba2db6dcd8c8")
print()

print("VERIFYING")
print("   the real message      :", verify(msg, (r, s), y))
print("   one character changed :", verify(b"abd", (r, s), y))
print("   r increased by one    :", verify(msg, (r + 1, s), y))
print()

print("THE SIZES. This is what DSA buys over RSA:")
print("   p is %d bits, but the SIGNATURE is two values below q," % p.bit_length())
print("   so it is %d bits in all, against %d for an RSA signature"
      % (2 * q.bit_length(), p.bit_length()))
print("   with the same modulus. The signature is %.0f%% of the size."
      % (100 * 2 * q.bit_length() / p.bit_length()))
print()

print("WHY r AND s ARE BOTH REDUCED MOD q. Everything the verifier computes is")
print("brought back into the subgroup of order q, which is why the numbers stay")
print("short while the discrete logarithm stays hard in the group of order p - 1.")
print()

print("AND THE MISTAKE THAT DESTROYS IT: a reused k.")
msg2 = b"xyz"
r2, s2 = sign(msg2, k)
print("   two signatures with the same k share r:", r == r2)
# s1 - s2 = k^-1 (H1 - H2) mod q  =>  k = (H1 - H2) / (s1 - s2)
k_rec = ((H(msg) - H(msg2)) * modinv((s - s2) % q, q)) % q
x_rec = ((s * k_rec - H(msg)) * modinv(r, q)) % q
print("   k recovered from the two signatures :", k_rec == k)
print("   and the PRIVATE KEY from k          :", x_rec == x)
print("   Two signatures, no key, and the attacker now holds x. This is the")
print("   same algebra as the ElGamal chapter, on the scheme everybody deployed.")
munotes.in357

The Digital Signature Standard, and the DSA Algorithm

THE PARAMETERS, checked before use:
   p has 512 bits, q has 160 bits
   q divides p - 1                : True
   g has order q, that is g^q = 1 : True
   g is not 1                     : True

   p, q and g are PUBLIC and may be shared by a whole community.
   x is the private key, y = g^x mod p is the public key.
   y begins 19131871d75b1612a819f29d...

SIGNING the message 'abc' with the published per-message secret k:
   r = g^k mod p, then mod q      = 8bac1ab66410435cb7181f95b16ab97c92b341c0
   s = k inverse (H(M) + x r) mod q = 41e2345f1f56df2458f426d155b4ba2db6dcd8c8
   the published values for these parameters are
     r = 8bac1ab66410435cb7181f95b16ab97c92b341c0
     s = 41e2345f1f56df2458f426d155b4ba2db6dcd8c8
   they agree: True

VERIFYING
   the real message      : True
   one character changed : False
   r increased by one    : False

THE SIZES. This is what DSA buys over RSA:
   p is 512 bits, but the SIGNATURE is two values below q,
   so it is 320 bits in all, against 512 for an RSA signature
   with the same modulus. The signature is 62% of the size.

WHY r AND s ARE BOTH REDUCED MOD q. Everything the verifier computes is
brought back into the subgroup of order q, which is why the numbers stay
short while the discrete logarithm stays hard in the group of order p - 1.

AND THE MISTAKE THAT DESTROYS IT: a reused k.
   two signatures with the same k share r: True
   k recovered from the two signatures : True
   and the PRIVATE KEY from k          : True
   Two signatures, no key, and the attacker now holds x. This is the
   same algebra as the ElGamal chapter, on the scheme everybody deployed.
munotes.in358

The Digital Signature Standard, and the DSA Algorithm

Read six things out of that run.

munotes.in359

The Digital Signature Standard, and the DSA Algorithm

The parameters are checked before use. q divides p - 1, and g to the power q is 1 modulo p, so g really does generate a subgroup of order q. Those two conditions are what make the scheme work, and verifying them costs two lines.

r and s match the published values exactly. This is the check on the whole chapter, and it is why these particular parameters were used rather than freshly generated ones.

Verification succeeds and both failures are shown: a changed message and a changed r.

The sizes. With a 512-bit p and a 160-bit q, the signature is two values below q, so 320 bits against 512 for an RSA signature under a modulus of the same size: 62 per cent. At modern parameters, a 3,072-bit p with a 256-bit q, the ratio is far better still: 512 bits against 3,072.

Why both r and s are reduced modulo q. Everything the verifier computes is brought back into the subgroup of order q. That is the whole design: the arithmetic happens in a large group where the discrete logarithm is hard, and the results live in a small subgroup where the numbers are short.

And the reused k recovers the private key exactly. Two signatures share r, which is visible; subtracting the two s values eliminates x and gives k; substituting back gives x. Both checks print True. This is not a theoretical concern: it is how private keys have been extracted from devices whose random number generators were predictable.

Verification, and why it works

The verification is four steps and an answer should give them.

Step 1. Check that r and s are both in the range 1 to q - 1. A signature outside it is rejected without further work, and omitting this check has been a real vulnerability.

Step 2. Compute w, the inverse of s modulo q.

Step 3. Compute u1 as H(M) times w modulo q, and u2 as r times w modulo q.

Step 4. Compute v as g to the power u1 times y to the power u2, modulo p, then modulo q. Accept if v equals r.

Why it works. From the signing equation, s = k inverse times (H(M) + x r) modulo q, so k = s inverse times (H(M) + x r), which is w H(M) + w x r, which is u1 + x u2 modulo q. Then g to the power u1 times y to the power u2 is g to the power (u1 + x u2), since y is g to the power x, and that is g to the power k. Reducing modulo q gives r by definition. So the verifier reconstructs g to the power k without ever knowing k.

munotes.in360

The Digital Signature Standard, and the DSA Algorithm

What DSA cannot do

Two limitations, both examinable.

It cannot encrypt. The verification recomputes rather than recovers, so there is no way to use the scheme to carry a message. RSA can encrypt and sign; DSA signs only. A question asking which algorithm to choose should note this.

It cannot exchange a key. Diffie-Hellman does that, in the same setting. DSA and Diffie-Hellman are often deployed together precisely because they share parameters and complement each other.

And the practical limitation that governs everything: every signature needs a fresh, unpredictable k, and there is no way to check afterwards that one was used. A device with a weak random source produces signatures that look perfectly valid and leak the key.

Distinctions that carry marks

RSA approachDSS approach
Signature isone value, modulus-sizedtwo values, each below q
Verificationdecrypts and compares with the hashrecomputes and compares with r
Recovers anythingthe hashnothing
Can also encryptyesno
Per-message secretnonerequired, and fatal if reused
Hard problemfactoringdiscrete logarithm in a subgroup
Global parameters shared by a communitynoyes, p, q and g
ParameterWhat it isPublic
pa large primeyes, and may be shared
qa prime divisor of p - 1yes, and may be shared
gan element of order q modulo pyes, and may be shared
xthe user's private key, less than qno
yg to the power x modulo pyes
ka fresh per-message secret, less than qno, and never reused

What beginners get wrong here

Saying DSA encrypts. It cannot. Verification recomputes rather than recovers.

Reusing k, or generating it from a weak source. Two signatures under one k give the private key exactly, as the run shows.

Forgetting the range check on r and s. Step 1 of verification, and omitting it has been a real vulnerability.

Confusing q with p. p is the large modulus; q is the small prime divisor of p - 1 and is what bounds the signature's size. The exponentiation is modulo p and the reduction is modulo q.

Calling DSA and DSS the same thing. DSS is the standard, FIPS 186; DSA is the algorithm it specified. The distinction matters in the next chapter, where the standard keeps its name and the algorithm is removed from it.

munotes.in361

The Digital Signature Standard, and the DSA Algorithm

Quick revision

  • DSS is FIPS 186, the standard; DSA is the algorithm it specified, built on ElGamal and Schnorr, with SHA for hashing.
  • Global parameters p, q, g, shareable by a community; private x below q; public y = g to the power x mod p.
  • Sign: r = (g to the power k mod p) mod q, and s = k inverse times (H(M) + x r) mod q.
  • Verify: check r, s in range; w = s inverse mod q; u1 = H(M) w, u2 = r w; v = (g to the power u1 times y to the power u2 mod p) mod q; accept if v = r.
  • Why it works: u1 + x u2 is k modulo q, so the verifier reconstructs g to the power k without knowing k.
  • Sizes: signature is two values below q, 320 bits against RSA's 512 for a 512-bit modulus, and 512 against 3,072 at modern parameters.
  • DSA cannot encrypt and cannot exchange keys. Verification recomputes; it recovers nothing.
  • A reused k gives the private key exactly. The two signatures visibly share r.

Test yourself

1. Give the DSA signing equations and name the parameters. The public global parameters are a large prime p, a prime q dividing p - 1, and g of order q modulo p. The private key x is less than q and the public key is y equal to g to the power x modulo p. With a fresh per-message secret k less than q, the signature is r equal to g to the power k modulo p then modulo q, and s equal to the inverse of k modulo q times the quantity H(M) plus x r, modulo q.

2. Set out the verification. Reject unless r and s both lie between 1 and q - 1. Compute w as the inverse of s modulo q, then u1 as H(M) times w modulo q and u2 as r times w modulo q. Compute v as g to the power u1 times y to the power u2, modulo p and then modulo q. Accept if v equals r.

3. Why does the verification work? Because from the signing equation k equals w times H(M) plus w x r modulo q, which is u1 plus x u2. Since y is g to the power x, the product g to the power u1 times y to the power u2 equals g to the power u1 + x u2, which is g to the power k. Reducing modulo q gives r by definition, so the verifier reconstructs g to the power k without ever learning k.

munotes.in362

The Digital Signature Standard, and the DSA Algorithm

4. How does the DSS approach differ structurally from the RSA approach? RSA encrypts the hash with the private key, producing a single modulus-sized value, and verification decrypts it and compares with the hash. DSA feeds the hash, a per-message secret, the private key and the global parameters into a signature function producing two short values, and verification feeds the hash, the signature and the public key into a function that recomputes a value and compares it with r. Nothing is recovered, which is why DSA cannot be used to encrypt.

5. What are the sizes, and why is the signature short? The signature is two values each less than q, so its length is twice the length of q rather than the length of p. With a 512-bit p and a 160-bit q that is 320 bits against RSA's 512 for the same modulus size, and at modern parameters of a 3,072-bit p with a 256-bit q it is 512 bits against 3,072. The arithmetic happens in the large group, where the discrete logarithm is hard, and the results are reduced into the small subgroup, where the numbers are short.

6. What happens if the per-message secret is reused? The two signatures share the same r, which anybody can see. Subtracting the two s values eliminates the private key and leaves an equation in k, which is solved directly; substituting k back into either signing equation gives the private key exactly. The chapter's run performs both recoveries and both succeed.

7. Distinguish DSS from DSA. DSS is the Digital Signature Standard, the Federal Information Processing Standard FIPS 186 itself. DSA is the Digital Signature Algorithm, the particular scheme that standard specified. The distinction matters because the standard continues to exist while the algorithm has been removed from it, which is the subject of the next chapter.

Contents This chapter on its own page

munotes.in363

Chapter Sixty

What the Signature Standard Says Today, and Why DSA Left It

Syllabus topic Module 2, "Digital Signatures and Authentication: Digital Signature Standard"

In one line

The standard kept its name and lost its algorithm. FIPS 186-5, February 2023, approves RSA, ECDSA and EdDSA for signing, and no longer approves DSA to sign anything.

What the standard actually says

FIPS 186-5's section 4 is one paragraph long, and it is the whole news:

"Prior versions of this standard specified the DSA. This standard no longer approves the DSA for digital signature generation."

Appendix E, the revision summary, repeats it and adds that the DSA specifications are no longer in FIPS 186-5 at all and must be read out of FIPS 186-4.

And the sentence that follows it is the one students miss: the DSA may still be used to verify signatures generated before the new standard's implementation date. This is the pattern seen already with 3DES, with SHA-1 and with RC4, and it is worth stating as a rule rather than learning four times:

When a standard retires an algorithm, generation stops and verification continues. New protection must use something approved; already-protected data must remain readable, or the retirement would destroy records it was meant to protect.

The three that are approved

FIPS 186-5's introduction says three techniques are approved, and names them.

Where it is specifiedHard problem
RSAIETF RFC 8017, with extra restrictions in FIPS 186-5 s.5.4factoring
ECDSAin FIPS 186-5 itself, curves in SP 800-186, deterministic variant in RFC 6979discrete logarithm on an elliptic curve
EdDSAIETF RFC 8032, curves in SP 800-186discrete logarithm on an Edwards curve

Note what happened to the specification work. ECDSA used to live in an ANSI standard, X9.62, which was withdrawn, so FIPS 186-5 had to write ECDSA out in full. RSA never lived in a FIPS at all; the standard approves an IETF document and adds restrictions. So "the standard" today is a set of pointers plus conditions, not a single self-contained algorithm description.

Why DSA went, when nobody broke it

No attack forced this, and a question asking "why was DSA removed" wants reasons of a different kind.

Its security rested on a large modulus, and elliptic curves do the same job with far smaller numbers. The run below shows it: a public key of 256 bits against DSA's 3,072 for comparable strength.

Almost nothing used it. Deployment had moved to RSA and to ECDSA years earlier, so the standard was specifying an algorithm implementers no longer chose.

The per-message secret was a standing hazard. DSA fails completely if k repeats or is guessable, and the failure is silent: the signatures are valid. Both of the newer approved schemes have a deterministic form in which k is computed from the key and the message, so there is nothing to draw and nothing to get wrong. ECDSA's deterministic form is RFC 6979 and EdDSA is deterministic by construction.

munotes.in364

What the Signature Standard Says Today, and Why DSA Left It

That third reason is the examinable one, because it is a design lesson rather than a procurement note: the fix for a fragile random number was to stop needing a random number.

ECDSA, run against the vector RFC 6979 publishes

# The algorithm FIPS 186-5 approves in DSA's place, run against a published vector.
# Curve P-256 from FIPS 186-4 D.1.2.3; deterministic k from RFC 6979 section 3.2.
import hashlib, hmac

# ---- the curve, and a check that we typed it correctly -------------------
p = 115792089210356248762697446949407573530086143415290314195533631308867097853951
n = 115792089210356248762697446949407573529996955224135760342422259061068512044369
b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
Gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296
Gy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5
a = p - 3                      # FIPS 186-4: a = -3 for every prime curve

print('p is 2**256 - 2**224 + 2**192 + 2**96 - 1:',
      p == 2**256 - 2**224 + 2**192 + 2**96 - 1)
print('n matches the q RFC 6979 prints    :',
      n == 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551)
print('G satisfies y**2 = x**3 + a x + b   :',
      (Gy * Gy - (Gx**3 + a * Gx + b)) % p == 0)

# ---- point arithmetic ---------------------------------------------------
def add(P, Q):                          # None is the point at infinity
    if P is None: return Q
    if Q is None: return P
    x1, y1 = P; x2, y2 = Q
    if x1 == x2 and (y1 + y2) % p == 0: return None
    if P == Q: lam = (3 * x1 * x1 + a) * pow(2 * y1, -1, p) % p
    else:      lam = (y2 - y1) * pow(x2 - x1, -1, p) % p
    x3 = (lam * lam - x1 - x2) % p
    return (x3, (lam * (x1 - x3) - y1) % p)

def mul(k, P):
    R, Q = None, P
    while k:
        if k & 1: R = add(R, Q)
        Q = add(Q, Q); k >>= 1
    return R

G = (Gx, Gy)
print('n times G is the point at infinity  :', mul(n, G) is None)

# ---- RFC 6979 section 3.2: k from the key and the message, no randomness -
def bits2int(b_, qlen=256):
    v = int.from_bytes(b_, 'big')
    return v >> (len(b_) * 8 - qlen) if len(b_) * 8 > qlen else v

def deterministic_k(x, h1, H=hashlib.sha256):
    hlen = H().digest_size
    def int2octets(v): return v.to_bytes(32, 'big')
    def bits2octets(b_): return int2octets(bits2int(b_) % n)
    V = b'\x01' * hlen
    K = b'\x00' * hlen
    m = int2octets(x) + bits2octets(h1)
    K = hmac.new(K, V + b'\x00' + m, H).digest(); V = hmac.new(K, V, H).digest()
    K = hmac.new(K, V + b'\x01' + m, H).digest(); V = hmac.new(K, V, H).digest()
    while True:
        V = hmac.new(K, V, H).digest()
        k = bits2int(V)
        if 1 <= k < n: return k
        K = hmac.new(K, V + b'\x00', H).digest(); V = hmac.new(K, V, H).digest()

# ---- ECDSA --------------------------------------------------------------
def sign(x, msg, k=None):
    h1 = hashlib.sha256(msg).digest()
    e = bits2int(h1) % n
    if k is None: k = deterministic_k(x, h1)
    r = mul(k, G)[0] % n
    s = pow(k, -1, n) * (e + x * r) % n
    return k, r, s

def verify(Q, msg, r, s):
    if not (1 <= r < n and 1 <= s < n): return False
    e = bits2int(hashlib.sha256(msg).digest()) % n
    w = pow(s, -1, n)
    P = add(mul(e * w % n, G), mul(r * w % n, Q))
    return P is not None and P[0] % n == r

x = 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721
Q = mul(x, G)
print('public key Ux matches RFC 6979      :',
      Q[0] == 0x60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6)
print('public key Uy matches RFC 6979      :',
      Q[1] == 0x7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299)

k, r, s = sign(x, b'sample')
print()
print('message "sample", SHA-256, k derived not drawn')
print('  k =', format(k, '064X'))
print('  r =', format(r, '064X'))
print('  s =', format(s, '064X'))
print('  RFC 6979 A.2.5 prints exactly these three:',
      k == 0xA6E3C57DD01ABE90086538398355DD4C3B17AA873382B0F24D6129493D8AAD60
      and r == 0xEFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716
      and s == 0xF7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8)
print('  verifies                        :', verify(Q, b'sample', r, s))
print('  verifies against "Sample"       :', verify(Q, b'Sample', r, s))

k2, r2, s2 = sign(x, b'test')
print('message "test", same key')
print('  k differs from the "sample" one  :', k2 != k)
print('  k, r, s all match RFC 6979       :',
      (k2, r2, s2) ==
      (0xD16B6AE827F17175E040871A1C7EC3500192C4C92677336EC2537ACAEE0008E0,
       0xF1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367,
       0x019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083))

print('  signing "sample" again gives the same pair:', sign(x, b'sample')[1:] == (r, s))

# ---- the same flaw, and why the deterministic rule removes it -----------
kfix = 0x1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF
_, rA, sA = sign(x, b'transfer 100', k=kfix)
_, rB, sB = sign(x, b'transfer 900', k=kfix)
eA = bits2int(hashlib.sha256(b'transfer 100').digest()) % n
eB = bits2int(hashlib.sha256(b'transfer 900').digest()) % n
krec = (eA - eB) * pow(sA - sB, -1, n) % n
xrec = (sA * krec - eA) * pow(rA, -1, n) % n
print()
print('two signatures under one k: r is the same in both:', rA == rB)
print('  k recovered from the pair        :', krec == kfix)
print('  private key recovered from k     :', xrec == x)
print('  and a derived k cannot repeat across messages, because it is a')
print('  function of the key and the message:', sign(x, b'a')[0] != sign(x, b'b')[0])

# ---- what a signature costs ---------------------------------------------
print()
for name, sig, keybits in (('RSA-3072 (PKCS#1 v1.5)', 3072, 3072),
                           ('DSA, 3072-bit p, 256-bit q', 2 * 256, 3072),
                           ('ECDSA on P-256', 2 * 256, 256),
                           ('EdDSA, Ed25519', 512, 256)):
    print(f'  {name:<28} signature {sig:>5} bits   public key {keybits:>5} bits')
munotes.in365

What the Signature Standard Says Today, and Why DSA Left It

p is 2**256 - 2**224 + 2**192 + 2**96 - 1: True
n matches the q RFC 6979 prints    : True
G satisfies y**2 = x**3 + a x + b   : True
n times G is the point at infinity  : True
public key Ux matches RFC 6979      : True
public key Uy matches RFC 6979      : True

message "sample", SHA-256, k derived not drawn
  k = A6E3C57DD01ABE90086538398355DD4C3B17AA873382B0F24D6129493D8AAD60
  r = EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716
  s = F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8
  RFC 6979 A.2.5 prints exactly these three: True
  verifies                        : True
  verifies against "Sample"       : False
message "test", same key
  k differs from the "sample" one  : True
  k, r, s all match RFC 6979       : True
  signing "sample" again gives the same pair: True

two signatures under one k: r is the same in both: True
  k recovered from the pair        : True
  private key recovered from k     : True
  and a derived k cannot repeat across messages, because it is a
  function of the key and the message: True

  RSA-3072 (PKCS#1 v1.5)       signature  3072 bits   public key  3072 bits
  DSA, 3072-bit p, 256-bit q   signature   512 bits   public key  3072 bits
  ECDSA on P-256               signature   512 bits   public key   256 bits
  EdDSA, Ed25519               signature   512 bits   public key   256 bits
munotes.in366

What the Signature Standard Says Today, and Why DSA Left It

What that run establishes, in order.

The curve is checked, not trusted. The modulus has the special form the standard gives, the order matches the q printed in a different document by a different body, the base point satisfies the curve equation, and multiplying it by n gives the point at infinity. Four independent checks before a single signature is computed, because a mistyped curve parameter produces signatures that verify against themselves and mean nothing.

The signature equals the published one. The same k, the same r, the same s as RFC 6979's Appendix A.2.5 for P-256, SHA-256 and the message sample, and the same again for test. That is the strongest form of evidence available for a chapter like this: an implementation agreeing with somebody else's printed numbers.

Signing the same message twice gives the same signature. For a randomised scheme it would not, and this is what "deterministic" means in practice.

A one-letter change to the message fails verification. Sample against sample.

The same fatal flaw is demonstrated and then closed. Two messages signed under one fixed k share r, and from the pair the program recovers k and then the private key, both exactly. Then the last line shows why the deterministic rule removes the hazard: k is a function of the key and the message, so two different messages cannot produce the same k.

The sizes are the argument for curves. ECDSA and EdDSA sign in 512 bits with a 256-bit public key. DSA needed 3,072 bits of public key for the same signature length, and RSA needed 3,072 bits for both.

munotes.in367

What the Signature Standard Says Today, and Why DSA Left It

How the per-message secret is derived

RFC 6979 s.3.2 is worth understanding rather than memorising, because the shape recurs.

The key and the message hash are fed into an HMAC-based generator: two internal values K and V are initialised to fixed bytes, updated twice with the key and the message hash mixed in, and then V is advanced to produce candidate values until one lands in the range 1 to n - 1. Because the key is an input, nobody without the key can compute k; because the message is an input, k changes with the message; because nothing else is an input, the result is reproducible.

The thing to notice: this uses a MAC to build a generator, which is the same construction seen in the HMAC chapters. The primitives in this subject recombine.

Distinctions that carry marks

FIPS 186-4, 2013FIPS 186-5, February 2023
DSAspecified and approvedremoved; verification of old signatures only
RSAapproved, plus X9.31 variantapproved; X9.31 signatures removed
ECDSAapproved, by reference to X9.62approved, specified in full; deterministic form added
EdDSAnot presentapproved, RFC 8032, prehash form too
Binary curvesspecifiedmoved to SP 800-186 and deprecated
Curve detailsin the standardin SP 800-186
The retirements this book has metGenerationVerification or decryption
3DES, after 31 December 2023disallowedpermitted for already-encrypted data
DSA, FIPS 186-5not approvedpermitted for earlier signatures
SHA-1 for signaturesdisallowedpermitted for legacy verification
SHA-1 inside HMACstill approvedstill approved

What beginners get wrong here

Writing that DSA is the current standard. It is in the standard's history. The standard is FIPS 186-5 and DSA is not in it.

Concluding DSA was broken. No attack on DSA forced the change. Size, disuse and the fragility of k did.

Thinking a retirement makes old data unreadable. It never does. Verification and decryption continue.

Confusing ECDSA with EdDSA. Different curves and different constructions: ECDSA is DSA's equations moved onto an elliptic curve and needs a per-message secret, which RFC 6979 then derives; EdDSA is a different scheme, deterministic by design, on Edwards curves.

Believing a deterministic signature is weaker because it repeats. A signature is public. Repeating it leaks nothing, and the repetition is what proves the secret was not drawn from a weak source.

Quick revision

  • FIPS 186-5, February 2023, s.4: the standard "no longer approves the DSA for digital signature generation"; DSA may verify signatures made before the implementation date, and its specification now lives only in FIPS 186-4.
  • Three approved: RSA (RFC 8017 plus restrictions), ECDSA (specified in the standard, curves in SP 800-186, deterministic form RFC 6979), EdDSA (RFC 8032).
  • Why DSA went: key sizes, almost no deployment, and the silent, total failure on a repeated or guessable k.
  • The design lesson: the answer to a fragile random number was to derive it from the key and the message instead.
  • Sizes for comparable strength: ECDSA and EdDSA sign in 512 bits with a 256-bit public key; DSA needed a 3,072-bit public key; RSA needed 3,072 bits for both.
  • Retirement rule, every time: generation stops, verification and decryption continue.
  • The chapter's run reproduces RFC 6979's printed k, r and s for P-256 with SHA-256, recovers a private key from a reused k, and shows a derived k cannot repeat across messages.
munotes.in368

What the Signature Standard Says Today, and Why DSA Left It

Test yourself

1. State what FIPS 186-5 did to DSA, and what it left permitted. It removed DSA from the standard and no longer approves it for digital signature generation; its specification is now found only in FIPS 186-4. It remains permitted to use DSA to verify signatures generated before the new standard's implementation date.

2. Name the three approved techniques and where each is specified. RSA, specified in IETF RFC 8017 with additional restrictions imposed by FIPS 186-5; ECDSA, specified in FIPS 186-5 itself with its recommended curves in SP 800-186 and a deterministic variant in RFC 6979; and EdDSA, specified in IETF RFC 8032 with its curves also in SP 800-186.

3. Give three reasons DSA was removed, none of them an attack. Its parameters had to be large where an elliptic curve gives comparable strength with a public key an eighth of the size; deployment had long since moved to RSA and ECDSA, so the standard was specifying something few implementers chose; and its per-message secret was a standing hazard, because a repeated or predictable value gives up the private key while the signatures continue to verify normally.

4. How does RFC 6979 remove the need for a random number? It derives the per-message secret with an HMAC-based generator whose inputs are the private key and the message hash. Because the key is an input, nobody else can compute the value; because the message is an input, it changes from message to message; and because nothing else is an input, the same key and message always produce the same value, so the result is reproducible and no randomness is consumed.

5. Why is a deterministic signature not weaker for being repeatable? Because the signature is a public value, so an observer who sees the same signature twice learns only that the same message was signed twice, which was already visible. What the determinism buys is the removal of the per-message random number, and with it the possibility that a weak generator repeats a value and gives away the private key.

munotes.in369

What the Signature Standard Says Today, and Why DSA Left It

6. What is the general rule about retired algorithms, and give two examples from this book? Generation of new protection stops and use of existing protection continues. Triple DES was disallowed for encryption after 31 December 2023 but may still decrypt data already encrypted, and DSA is no longer approved to sign but may still verify signatures made earlier.

7. Distinguish ECDSA from EdDSA. ECDSA is the DSA construction transplanted onto an elliptic curve, so it still requires a per-message secret, which RFC 6979 supplies deterministically when the deterministic variant is used. EdDSA is a separate scheme defined on Edwards curves and is deterministic as designed rather than as an option. Both are approved by FIPS 186-5 and both take their recommended curves from SP 800-186.

Contents This chapter on its own page

munotes.in370

Chapter Sixty-One

Kerberos: The Problem It Solves

Syllabus topic Module 2, "Authentication Applications: Kerberos"

In one line

Kerberos is a trusted referee on a network: you prove who you are to it once, with your password, and it then vouches for you to every server you use, without your password ever crossing the network.

In the words an answer should use: Kerberos is an authentication service, designed at MIT for Project Athena, in which a trusted third party that shares a secret key with every user and every server issues time-limited, encrypted tickets, so that users and servers can prove their identities to each other across an open network using conventional (secret-key) cryptography.

Where it comes from

Kerberos was built at the Massachusetts Institute of Technology for Project Athena, a campus network of workstations and shared servers. Its original design was by Steve Miller and Clifford Neuman. Versions 1 to 3 were used only inside MIT; version 4 is the one that spread beyond it, and version 5 is the one in use today, specified in RFC 1510 in 1993 and then in RFC 4120 in July 2005.

Its designers' own paper, presented at the USENIX conference in February 1988, records the scale it was built for. A prototype went into production in September 1986, and from January 1987 Kerberos was Project Athena's only means of authenticating its 5,000 users, 650 workstations and 65 servers.

The name is the Greek spelling of Cerberus, the three-headed dog that guards the entrance to the underworld in Greek myth. The 1988 paper carried a drawing of the dog.

The setting: a network nobody fully controls

Picture a college computer laboratory. Sixty workstations sit in rows. Behind them, somewhere on the same network, are a file server that holds every student's work, a print server, a mail server and a server that runs the laboratory's database. Any student can sit at any workstation.

The 1988 paper states the fact that makes this hard, and it is worth reading as the whole problem in one sentence: users "have complete control of their workstations". A student can restart a machine, start it from their own operating system, or connect their own laptop to the same network. So the workstation cannot be trusted to say who is sitting at it, and the network cannot be trusted to carry anything secret.

Yet the file server must know, for every request, which student is asking, because it must hand Asha's files to Asha and to nobody else.

Three ways a server could decide who is asking

The 1988 paper sets out three possible answers, and the design only makes sense once the first two have been ruled out.

ApproachHow the server decidesWhen it is acceptable
Trust the workstationWhatever machine the user logged in to is believedOnly when every machine is under the organisation's strict control
Trust the host's wordEach host proves which host it is; the server then believes the host about which user is askingOnly when the hosts are trusted but the users may not be; the old rlogin and rsh checked the network address a connection came from
Make the user prove itThe user proves their identity to every service they use, and the service proves its own identity backAn open network, which is the laboratory above
munotes.in371

Kerberos: The Problem It Solves

Kerberos takes the third approach. Every request is authenticated as coming from a named user, not from a machine, and the server can be asked to prove itself too, because, in the paper's words, someone elsewhere on the network "may be masquerading as the given server".

What an attacker on such a network can do

Three threats follow directly from the setting, and every part of the design exists to defeat one of them.

  1. Pretending to be another user at a workstation. The student controls the machine, so anything the machine says about who is logged in can be made to say anything.
  2. Pretending to be another workstation. A server that trusts a network address trusts something any machine on the network can change. This is exactly why the second approach in the table fails.
  3. Listening, and replaying. Anything that crosses the network can be recorded. The 1988 paper defines the replay precisely: a message "stolen off the network and resent later". A recorded message that worked once may work again.

And a fourth, which the first three hide: a fake server. A machine that pretends to be the file server can collect whatever the students send it, which is why authentication must run in both directions.

Why "send the password to every server" fails

The obvious design is the one every student proposes first: each server asks for the user's password and checks it. It fails in five separate ways.

  1. The password crosses the network in the clear, and threat 3 records it. One capture and the attacker is the user, on every server, until the password is changed.
  2. Every server must hold every password. At Athena's scale that is 5,000 × 65 = 325,000 stored password records, where one database of 5,000 would do. Breaking into any one of 65 servers yields everybody's password.
  3. A fake server collects passwords simply by asking for them. The user has no way to tell the real file server from an impostor.
  4. The user types the password again for every service. People who must type a password many times a day choose short ones.
  5. Nothing stops a replay. Even a scrambled password, sent the same way each time, is a recording that can be replayed.
munotes.in372

Kerberos: The Problem It Solves

Every one of those five is an argument, not an opinion, and a question asking "why was Kerberos needed" is answered by stating them.

The four requirements

The 1988 paper lists four requirements the service had to meet. They are worth knowing by name because they are exactly what the design trades against each other.

RequirementWhat the paper means by itHow Kerberos meets it
SecureSomeone watching the network must not learn enough to impersonate a user; the authentication must not be the weak linkThe password never crosses the network, and everything that does is encrypted or useless when replayed
ReliableMany services depend on it, so if it fails, everything failsThe key database is replicated: one master copy and read-only copies on other machines, any of which can authenticate
TransparentIdeally the user is not aware that authentication is happeningThe password is typed once, at login, and everything after that happens silently
ScalableIt must work across many hosts, including ones that do not use itOne service for the whole campus, and realms (the chapter after next) to join separate organisations

The idea: a referee that knows every key

Kerberos is built on the trusted third party of the Needham and Schroeder protocol, with the timestamps that Denning and Sacco proposed, which is the ground the authentication protocols chapter covered. RFC 4120's own introduction names both.

Every party that uses Kerberos is a principal: a user, or a network service. The Kerberos server holds a database with one secret key for every principal.

  • A service's key is a long random key, generated when the service is registered and installed in a protected file on the server's machine (in version 4, /etc/srvtab).
  • A user's key is computed from the user's password by a one-way function, so the database holds the key, and not the password itself. The version 5 method, from RFC 3962, runs the password through PBKDF2 (a deliberately slow, repeated keyed hash) with a salt made of the realm name followed by the user's name, 4,096 times by default.

Because the referee shares a key with everyone, it can write a message that only one particular server can read, and put inside it a statement that the server will believe: this request comes from Asha. That sealed statement is a ticket.

The price, which the key management chapter named in advance: the referee knows every key. Whoever controls the Kerberos server controls every identity on the network, which is why the paper insists that the machine holding it be physically secure.

munotes.in373

Kerberos: The Problem It Solves

Building the design, one defect at a time

The dialogue in the next chapter is six messages long. It did not arrive in one piece, and the fastest way to understand it is to build it the way its problems force it.

Attempt 1: the password goes to each server

Rejected above, for five reasons. It is in the run below only so that its failure can be seen.

Attempt 2: one authentication server, and a ticket

Put a single authentication server (AS) in charge of passwords. Asha sends it her name and her password and the name of the server she wants; it checks the password and returns a ticket: her name, her workstation's address and the server's name, all encrypted under the server's secret key. She hands the ticket to the server, which decrypts it and believes it, because only the AS could have made it.

That fixes two things. Only one machine holds passwords, and the ticket cannot be forged or altered by Asha, because she does not have the server's key. Two defects remain. Her password still crosses the network, now to the AS. And she needs a new ticket, and so types her password again, for every new server she uses.

Attempt 3: the password never travels, and it is typed once

Two changes fix both.

The password stops travelling. Asha sends only her name. The AS encrypts its reply under Asha's key, the one derived from her password. The workstation asks her for the password, derives the key, and decrypts the reply. The password never leaves the keyboard: what crosses the network is a reply that only her password can open.

The password is typed once. The AS no longer issues tickets for each server. It issues one special ticket, a ticket-granting ticket (TGT), for a second server, the ticket-granting server (TGS). Whenever Asha wants a new service she presents the TGT to the TGS, which issues the ticket for that service without asking for her password. The workstation keeps the TGT and forgets the password.

Tickets now carry the time they were issued and a lifetime, so a ticket stops working when it is old. At Athena the TGT's lifetime was eight hours: a working day.

What is still missing

Attempt 3 is close, and its remaining defect is the one the whole of version 4 is designed around. A ticket proves nothing about who is presenting it. It says "this is for Asha", and anyone who copies it off the network can present it. The server can check the address inside against the address the request came from, but threat 2 says an address can be changed. So a thief with a copied ticket and a changed address is Asha, until the ticket expires.

munotes.in374

Kerberos: The Problem It Solves

The lifetime cannot fix that. A short lifetime shrinks the thief's window and makes Asha type her password all day; a long one is convenient and hands the thief the whole day. The fix is not in the ticket at all. It is a second, fresh item that only the real Asha can produce, which the next chapter calls the authenticator. And one more defect remains untouched: nothing yet proves to Asha that the server is the real one.

The run

The listing builds attempts 1 and 3 and attacks both. Tickets are sealed with encrypt-then-MAC, a stand-in for the DES of version 4 and the AES of version 5, so that any change to a ticket is detected; the protocol's logic, not the cipher, is the subject here. Asha's key is derived exactly as RFC 3962 derives it, and the derivation is first checked against the output the RFC prints.

# The problem Kerberos solves, run: a password that travels, and a ticket that is not proof.
import hashlib, hmac, json, random

rng = random.Random(610)                 # seeded, so every run prints the same thing

def string_to_key(password, realm, name):
    # RFC 3962's first step: PBKDF2 with HMAC-SHA1, salt = realm + name, 4,096 rounds
    return hashlib.pbkdf2_hmac('sha1', password.encode(), (realm + name).encode(), 4096, 16)

rfc = hashlib.pbkdf2_hmac('sha1', b'password', b'ATHENA.MIT.EDUraeburn', 1200, 16)
print('PBKDF2 step matches RFC 3962 Appendix B (1,200 rounds):',
      rfc.hex() == '5c08eb61fdf71e4e4ec3cf6ba1f5512b')

def keystream(key, nonce, n):
    blocks = (hashlib.sha256(key + nonce + i.to_bytes(4, 'big')).digest()
              for i in range(n // 32 + 1))
    return b''.join(blocks)[:n]

def seal(key, record):
    # encrypt, then MAC: a stand-in for version 4's DES or version 5's AES
    nonce = rng.getrandbits(64).to_bytes(8, 'big')
    plain = json.dumps(record, sort_keys=True).encode()
    body = nonce + bytes(p ^ s for p, s in zip(plain, keystream(key, nonce, len(plain))))
    return body + hmac.new(key, body, hashlib.sha256).digest()

def unseal(key, blob):
    body, tag = blob[:-32], blob[-32:]
    if not hmac.compare_digest(tag, hmac.new(key, body, hashlib.sha256).digest()):
        raise ValueError('integrity check failed')
    nonce, data = body[:8], body[8:]
    return json.loads(bytes(c ^ s for c, s in zip(data, keystream(key, nonce, len(data)))))

REALM = 'COLLEGE.EXAMPLE'
wire = []                                 # everything anyone on the LAN can record

def send(frm, to, what):
    wire.append((frm, to, what))
    return what

# ---- attempt 1: the password goes to every server ---------------------------
print()
print('ATTEMPT 1: Asha sends her password to each server she uses')
stored = {}
for server in ('files', 'print', 'mail'):
    stored[server] = {'asha': 'monsoon-lab-7'}          # each server must hold it
    send('10.0.5.21', server, 'user=asha password=monsoon-lab-7')
print('  copies of her password held on servers :', len(stored))
print('  password readable in the wire recording:',
      any('monsoon-lab-7' in str(w) for w in wire))

# ---- attempt 3: one authentication server, and a ticket-granting ticket -----
print()
print('ATTEMPT 3: the password never leaves the keyboard')
wire.clear()
K = {'asha': string_to_key('monsoon-lab-7', REALM, 'asha'),
     'tgs':   rng.getrandbits(128).to_bytes(16, 'big'),
     'files': rng.getrandbits(128).to_bytes(16, 'big')}
LIFETIME = 8 * 3600                       # Athena's 1988 figure: eight hours

def authentication_server(user, service, addr, now):
    tgt = seal(K['tgs'], {'user': user, 'addr': addr, 'service': service,
                          'issued': now, 'life': LIFETIME})
    return seal(K[user], {'tgt': tgt.hex()})              # readable only with Kc

def ticket_granting_server(user, service, tgt, addr, now):
    t = unseal(K['tgs'], tgt)
    if t['user'] != user or t['addr'] != addr:
        return None
    if not t['issued'] <= now < t['issued'] + t['life']:
        return None
    return seal(K[service], {'user': user, 'addr': addr, 'service': service,
                             'issued': now, 'life': LIFETIME})

def file_server(user, ticket, addr, now):
    try:
        t = unseal(K['files'], ticket)
    except ValueError as e:
        return 'refused: ' + str(e)
    if t['user'] != user or t['addr'] != addr:
        return 'refused: wrong user or address'
    if not t['issued'] <= now < t['issued'] + t['life']:
        return 'refused: ticket expired'
    return 'access granted to ' + t['user']

now = 1000
reply = send('as', '10.0.5.21', authentication_server('asha', 'tgs', '10.0.5.21', now))
typed = string_to_key('monsoon-lab-7', REALM, 'asha')     # typed at the workstation
tgt = bytes.fromhex(unseal(typed, reply)['tgt'])
try:
    unseal(string_to_key('monsoon-lab-8', REALM, 'asha'), reply)
except ValueError as e:
    print('  the reply opened with a wrong password :', e)

send('10.0.5.21', 'tgs', ('asha', 'files', tgt))
ticket = send('tgs', '10.0.5.21',
              ticket_granting_server('asha', 'files', tgt, '10.0.5.21', now + 5))
print('  password readable in the wire recording:',
      any('monsoon-lab-7' in str(w) for w in wire))
print('  Asha, from her own machine             :',
      file_server('asha', ticket, '10.0.5.21', now + 10))

altered = bytearray(ticket)
altered[20] ^= 1                                          # she edits one bit
print('  Asha, with one bit of the ticket edited:',
      file_server('asha', bytes(altered), '10.0.5.21', now + 20))

# ---- and the defect: a copied ticket is as good as the original -------------
print()
print('THE DEFECT: Ravi records the ticket off the wire')
copied = [w[2] for w in wire if w[1] == '10.0.5.21' and w[0] == 'tgs'][0]
print('  Ravi, from his own address             :',
      file_server('asha', copied, '10.0.5.77', now + 60))
print('  Ravi, after changing his address       :',
      file_server('asha', copied, '10.0.5.21', now + 60))
print('  Ravi, nine hours later                 :',
      file_server('asha', copied, '10.0.5.21', now + 9 * 3600))

# ---- what the lifetime trades away -------------------------------------------
print()
print('A copied ticket is usable until it expires, so the lifetime is a trade:')
for hours in (1, 8, 21.25):
    logins = 1 if hours >= 8 else round(8 / hours)
    typing = '1 password entry' if logins == 1 else f'{logins} password entries'
    print(f'  lifetime {hours:>5} h: a stolen ticket works for up to {hours:>5} h;'
          f' an 8 h day needs {typing}')
munotes.in375

Kerberos: The Problem It Solves

PBKDF2 step matches RFC 3962 Appendix B (1,200 rounds): True

ATTEMPT 1: Asha sends her password to each server she uses
  copies of her password held on servers : 3
  password readable in the wire recording: True

ATTEMPT 3: the password never leaves the keyboard
  the reply opened with a wrong password : integrity check failed
  password readable in the wire recording: False
  Asha, from her own machine             : access granted to asha
  Asha, with one bit of the ticket edited: refused: integrity check failed

THE DEFECT: Ravi records the ticket off the wire
  Ravi, from his own address             : refused: wrong user or address
  Ravi, after changing his address       : access granted to asha
  Ravi, nine hours later                 : refused: ticket expired

A copied ticket is usable until it expires, so the lifetime is a trade:
  lifetime     1 h: a stolen ticket works for up to     1 h; an 8 h day needs 8 password entries
  lifetime     8 h: a stolen ticket works for up to     8 h; an 8 h day needs 1 password entry
  lifetime 21.25 h: a stolen ticket works for up to 21.25 h; an 8 h day needs 1 password entry
munotes.in376

Kerberos: The Problem It Solves

What the run establishes, in order.

The key derivation is the real one. Before any key is made, the PBKDF2 step reproduces RFC 3962 Appendix B's printed value for the password password, the salt ATHENA.MIT.EDUraeburn and 1,200 rounds. The salt is that realm followed by that user's name, which is RFC 4120's default rule.

Attempt 1 leaks the password to anyone listening, and leaves a copy on every server.

Attempt 3 does not. The recording of every message on the network contains no trace of the password, and a reply opened with the wrong password fails its integrity check instead of yielding a ticket.

The ticket is sealed against its owner. Asha changes one bit of it and the server refuses it. She cannot read it or edit it, only pass it on.

And the defect is real. Ravi records the ticket. From his own address the server refuses it, because the address inside does not match. From Asha's address, which he has only to claim, the server grants him access as Asha. Only the expiry stops him, nine hours later.

The lifetime is a trade, not a fix. One hour protects better and costs eight password entries a day; eight hours costs one entry and gives a thief the day. The last line uses version 4's longest possible lifetime, 21.25 hours, which the chapter after next derives.

Key terms

TermMeaning
PrincipalAny party Kerberos authenticates: a user or a network service
RealmOne organisation's Kerberos installation: its server and the principals registered with it
Key distribution centre (KDC)The Kerberos server: the authentication server and the ticket-granting server, usually on one machine
Authentication server (AS)The part that checks a user at login and issues the ticket-granting ticket
Ticket-granting server (TGS)The part that issues tickets for individual services, on presentation of a ticket-granting ticket
TicketA statement of the client's identity, sealed under the key of the one server it is for; reusable until it expires
Ticket-granting ticket (TGT)The ticket whose server is the TGS; obtained once per login
LifetimeHow long a ticket may be used after it is issued
Session keyA fresh key the KDC makes for one client and one server, carried inside the ticket (the next chapter)
AuthenticatorA fresh, one-use item that proves the presenter of a ticket holds its session key (the next chapter)
munotes.in377

Kerberos: The Problem It Solves

Distinctions that carry marks

Password to every serverKerberos
Password crosses the networkevery timenever
Where passwords are heldon every serverin one database, as derived keys
Password typedfor every serviceonce per login
A fake server cancollect the passwordlearn nothing it can use
Kind of cryptographynone, or ad hocconventional (secret-key) encryption throughout
AuthenticationAuthorisation
Question it answerswho is askingwhat they may do
Kerberosprovides itleaves it to the server, which decides after learning who is asking

What beginners get wrong here

Saying Kerberos uses public-key cryptography. It does not. RFC 4120 describes Kerberos as authenticating "by using conventional (shared secret key) cryptography". Public-key extensions exist, and they are outside the core protocol.

Saying the password is sent encrypted. It is not sent at all. What is sent is the user's name, and what comes back is a reply encrypted under a key derived from the password.

Treating a ticket as proof of identity. A ticket proves that the KDC issued it for somebody; it does not prove that the person presenting it is that somebody. That is the job of the authenticator, and it is the most examined idea in the next chapter.

Confusing authentication with authorisation. Kerberos tells the file server who is asking. Whether that person may read a given file is the file server's decision.

Believing Kerberos needs no trust. It needs complete trust in one machine. The KDC holds every key, so whoever controls it controls every identity.

Where Kerberos stops

RFC 4120 s.1.6 lists what the protocol assumes rather than solves, and each item is a likely short question.

  • Denial of service is not solved. An attacker can stop the authentication steps from happening.
  • Principals must keep their keys secret. Anyone who steals a key is that principal.
  • Password guessing is not solved. A recorded reply encrypted under a key derived from a weak password can be attacked offline, one dictionary word at a time. The chapter after next shows what version 5 did to make this harder.
  • Clocks must be loosely synchronised, typically within 5 minutes, and a network clock service must itself be secured. This is the price of timestamps that the authentication protocols chapter named, now paid.
  • Principal names are not recycled quickly, so that a new user never inherits an old user's permissions.
munotes.in378

Kerberos: The Problem It Solves

The 1988 paper adds its own open problems, all still instructive: choosing a ticket lifetime, letting a server act on a user's behalf, and trusting a public workstation that someone may have altered to record passwords as they are typed.

Quick revision

  • Kerberos: MIT, Project Athena; trusted third party; conventional (secret-key) cryptography; the password is typed once and never crosses the network.
  • Scale it was built for (1988 paper): 5,000 users, 650 workstations, 65 servers; sole authentication at Athena from January 1987.
  • Three approaches: trust the workstation, trust the host's word, make the user prove it to each service. Only the third works on an open network.
  • Threats: impersonating a user at a workstation, impersonating a workstation by address, eavesdropping and replay, and a fake server.
  • Four requirements: secure, reliable, transparent, scalable.
  • Design steps: one AS and a ticket; the reply encrypted under the password-derived key; a TGT and a TGS so the password is typed once; tickets with a lifetime.
  • Remaining defect: a copied ticket works for the thief until it expires, so a fresh authenticator is needed, and the server must prove itself too.
  • Not solved by Kerberos (RFC 4120 s.1.6): denial of service, password guessing, stolen keys; clocks within about 5 minutes are assumed.

Test yourself

1. Why can a server on an open campus network not simply trust the workstation to identify its user? Because the user controls the workstation. They can restart it, run their own operating system on it, or attach their own machine to the network, so anything the workstation reports about who is logged in can be made to say whatever its user wants. Only the user proving their own identity to the service, independently of the machine, can be relied on.

2. Give three reasons why sending the password to every server is unacceptable. The password crosses the network in the clear, where anyone listening can record it and then impersonate the user everywhere. Every server must then hold every user's password, so breaking into any one server exposes all of them. And a fake server can collect passwords merely by asking, since the user cannot tell it from the real one. Further reasons: the password must be typed for every service, and the same message can be replayed.

munotes.in379

Kerberos: The Problem It Solves

3. How does Kerberos let a user authenticate without sending the password? The user sends only their name. The authentication server replies with a message encrypted under a key derived from the user's password, which it holds in its database. The workstation asks the user for the password, derives the same key and decrypts the reply locally. Only someone who knows the password can open the reply, and the password itself never crosses the network.

4. What is the ticket-granting server for? It lets the password be typed once per session. At login the authentication server issues a single ticket-granting ticket. Whenever the user needs a ticket for a new service, the workstation presents the ticket-granting ticket to the ticket-granting server, which issues the service ticket without the password being needed again.

5. A ticket is encrypted under the server's key and cannot be forged. Why is it still not enough to authenticate its presenter? Because anyone who copies it off the network can present it. The ticket says whom it was issued to, not who is presenting it, and an address check inside it can be defeated by changing one's network address. A thief can therefore use a copied ticket until it expires. The fix is an authenticator, a fresh item that only the holder of the ticket's session key can produce.

6. State the four requirements Kerberos was designed to meet, and one thing it does not solve. It had to be secure, reliable, transparent and scalable. It does not solve denial of service, and it does not solve password guessing: a recorded reply encrypted under a key derived from a weak password can be attacked offline.

Contents This chapter on its own page

munotes.in380

Chapter Sixty-Two

The Kerberos Dialogue, Step by Step

Syllabus topic Module 2, "Authentication Applications: Kerberos"

In one line

Kerberos version 4 authenticates a user in six messages: two to log in and get a ticket-granting ticket, two to swap it for a ticket to one server, and two to present that ticket to the server, which can prove itself in return.

In the words an answer should use, which are RFC 4120's own names for the three: the Kerberos dialogue consists of the authentication service exchange (messages 1 and 2), made once at login, which gives the client a ticket-granting ticket; the ticket-granting service exchange (messages 3 and 4), made once for each service the client uses, which gives it a service ticket; and the client/server authentication exchange (messages 5 and 6), made at the start of each session with that service, which authenticates the client to the server and, when asked, the server to the client.

Version 4's exchange is worked here message by message because it is the one the prescribed textbook sets out. Version 5 keeps the same shape, and the next chapter lists what it changed.

The notation, read this first

Every symbol in the six messages is one of these. Two vertical bars, ||, mean "followed by": the fields either side of them are joined into one message, in that order.

SymbolMeaning
Cthe client: the program on Asha's workstation acting for her
ASthe authentication server
TGSthe ticket-granting server
Vthe server Asha wants to use (the file server, in the example)
IDc, IDtgs, IDvthe names of the user, the TGS and the server
ADcthe network address of Asha's workstation
TS1 to TS5timestamps, the time at which each item was made
Lifetime2, Lifetime4how long each ticket may be used after it was issued
KcAsha's secret key, derived from her password
Ktgs, Kvthe secret keys of the TGS and of the server, known also to the AS
Kc,tgsa session key, made by the AS, for Asha and the TGS
Kc,va session key, made by the TGS, for Asha and the server
E(K, [X])the fields X, encrypted under the key K

The whole dialogue at a glance

(a) The authentication service exchange: once, at login

  (1)  C -> AS    IDc || IDtgs || TS1
  (2)  AS -> C    E(Kc, [Kc,tgs || IDtgs || TS2 || Lifetime2 || Ticket_tgs])

       Ticket_tgs = E(Ktgs, [Kc,tgs || IDc || ADc || IDtgs || TS2 || Lifetime2])

(b) The ticket-granting service exchange: once for each service used

  (3)  C -> TGS   IDv || Ticket_tgs || Authenticator_c
  (4)  TGS -> C   E(Kc,tgs, [Kc,v || IDv || TS4 || Ticket_v])

       Authenticator_c = E(Kc,tgs, [IDc || ADc || TS3])
       Ticket_v        = E(Kv, [Kc,v || IDc || ADc || IDv || TS4 || Lifetime4])

(c) The client/server authentication exchange: at each session with it

  (5)  C -> V     Ticket_v || Authenticator_c
  (6)  V -> C     E(Kc,v, [TS5 + 1])          (only when mutual authentication is asked for)

       Authenticator_c = E(Kc,v, [IDc || ADc || TS5])
munotes.in381

The Kerberos Dialogue, Step by Step

Three keys protect the three replies, and learning which is which is half of this topic. Message 2 is under Asha's password key Kc; message 4 is under the first session key Kc,tgs; message 6 is under the second session key Kc,v. Each ticket is under the key of the server it is for, and each authenticator is under the session key that ticket carries.

Why there are three exchanges

Each exchange happens at a different rate, and that is the whole reason they are separate.

  • The authentication service exchange happens once per login. It is the only one that uses the password, so the password is needed once a day.
  • The ticket-granting exchange happens once per service, the first time Asha uses the file server, the printer or the mail server that day.
  • The client/server exchange happens once per session with a service. A second connection to the file server later the same morning needs only messages 5 and 6: the ticket is cached, and only a new authenticator is made.

The 1988 paper names the same three phases: obtain credentials, request authentication for a specific service, present the credentials to the end server.

Message by message

Message 1: Asha asks for a ticket-granting ticket

(1)  C -> AS    IDc || IDtgs || TS1

At login the workstation sends, in the clear, Asha's name, the name of the ticket-granting server, and the time.

  • IDc tells the AS whose key to use for the reply, and so who the ticket will name.
  • IDtgs says what is being asked for: a ticket for the TGS, which is what makes the result a ticket-granting ticket.
  • TS1 lets the AS see that the workstation's clock agrees with its own, which every later check will depend on.

Nothing in message 1 is secret, and nothing in it proves who sent it. Anyone can send it with Asha's name in it. That is safe for the reason the next message shows, with one exception the next chapter takes up: the reply it earns can be attacked offline with password guesses.

Message 2: the AS replies, under Asha's password

(2)  AS -> C    E(Kc, [Kc,tgs || IDtgs || TS2 || Lifetime2 || Ticket_tgs])
     Ticket_tgs = E(Ktgs, [Kc,tgs || IDc || ADc || IDtgs || TS2 || Lifetime2])

The AS looks Asha up, makes a fresh random session key Kc,tgs, builds the ticket-granting ticket, and sends both back encrypted under Kc, the key derived from her password.

munotes.in382

The Kerberos Dialogue, Step by Step

Only now does the workstation ask Asha for her password. The 1988 paper describes exactly this order: the response arrives, the user is prompted, the password is converted to a key and used to decrypt the response, and then the password and the key are erased from memory. What the workstation keeps is the ticket and the session key.

The outer fields are for Asha:

  • Kc,tgs, the session key she will share with the TGS, so she can talk to it without her password.
  • IDtgs, confirming that this reply is the ticket she asked for.
  • TS2 and Lifetime2, so her workstation knows when the ticket will expire.

The ticket's fields are for the TGS, and each one closes a hole:

  • Kc,tgs is inside the ticket too. This is how the TGS learns the session key: it keeps no list of tickets it has issued; the ticket carries its own key to it, sealed.
  • IDc names whose ticket it is.
  • ADc ties the ticket to the workstation's address, so that a copy presented from elsewhere is refused.
  • IDtgs names the one server that may accept it, so that it cannot be presented anywhere else.
  • TS2 and Lifetime2 fix its expiry. At Athena that was eight hours.

The ticket is encrypted under Ktgs, which Asha does not have: she cannot read it, and she cannot change the name, address or expiry inside it without the TGS noticing.

Message 3: Asha asks the TGS for a ticket to the file server

(3)  C -> TGS   IDv || Ticket_tgs || Authenticator_c
     Authenticator_c = E(Kc,tgs, [IDc || ADc || TS3])

IDv names the server she wants. Ticket_tgs is passed on unchanged. And the authenticator is new: her name, her address and the current time, encrypted under the session key from message 2.

The authenticator is the answer to the defect the previous chapter ended on. A copied ticket cannot be used by a thief, because the ticket must travel with an authenticator, and an authenticator can only be made by someone who holds Kc,tgs. Only Asha holds it: it reached her encrypted under her password.

The TGS then checks, in order: it decrypts the ticket with Ktgs and takes out Kc,tgs; it checks the ticket has not expired; it decrypts the authenticator with Kc,tgs; it checks that the name and address in the authenticator match those in the ticket, and that the address matches the address the message actually came from; and it checks that TS3 is close to its own clock.

munotes.in383

The Kerberos Dialogue, Step by Step

Message 4: the TGS replies, under the session key

(4)  TGS -> C   E(Kc,tgs, [Kc,v || IDv || TS4 || Ticket_v])
     Ticket_v = E(Kv, [Kc,v || IDc || ADc || IDv || TS4 || Lifetime4])

Message 4 has the same shape as message 2, one level down. A new session key Kc,v for Asha and the file server, and a ticket for the file server, sealed under Kv, the file server's key.

The reply is encrypted under Kc,tgs, not under Kc. That is why Asha is not asked for her password again: the key that opens message 4 is one her workstation already holds.

One rule about Lifetime4 is easy to miss, and the 1988 paper states it: a service ticket's lifetime is the smaller of what remains of the ticket-granting ticket and the service's own default. A service ticket can never outlive the login that produced it.

Message 5: Asha presents the ticket to the file server

(5)  C -> V     Ticket_v || Authenticator_c
     Authenticator_c = E(Kc,v, [IDc || ADc || TS5])

The ticket, unchanged, and a new authenticator under Kc,v. The file server makes the checks the TGS made:

  1. It decrypts the ticket with its own key Kv, and so learns Kc,v and whom the ticket names.
  2. It checks the ticket has not expired.
  3. It decrypts the authenticator with Kc,v, which proves the presenter holds that key.
  4. It checks that the name and address in the authenticator match the ticket.
  5. It checks that the address matches the address the request came from.
  6. It checks that TS5 is within the allowed difference from its own clock, typically five minutes.
  7. It checks that it has not seen this authenticator before.

The seventh check has a history worth knowing. The 1988 paper says the server is allowed to keep a list of recent requests and discard a repeat. The 1994 paper by the version 5 designers records that MIT's own version 4 implementation did not keep that list. RFC 4120 made it a MUST for version 5: a server must remember every authenticator presented within the allowed clock difference, and refuse a repeat. The run below shows why.

Message 6: the server proves itself, when asked

(6)  V -> C     E(Kc,v, [TS5 + 1])

If Asha asked for mutual authentication, the server takes the timestamp from her authenticator, adds one, encrypts it under Kc,v and sends it back.

Why this proves the server is real. Kc,v reached the server only inside the ticket, and the ticket is sealed under Kv. A server that can return a correct message 6 therefore holds Kv, which only the real file server does.

Why one is added. If the reply were TS5 itself, an impostor could simply send Asha's own authenticator back to her, since it already contains TS5 encrypted under Kc,v. That is the backward replay of the authentication protocols chapter. Adding one forces the server to decrypt, change and re-encrypt, which only a holder of the key can do.

munotes.in384

The Kerberos Dialogue, Step by Step

The run

The listing runs all six messages for Asha at 09:00 in a college laboratory, and then everything an attacker on the same network would try. Times are seconds after midnight; the clock window is five minutes; tickets are sealed by encrypt-then-MAC as a stand-in for version 4's DES, so any tampering or wrong key is detected. Every verdict printed is the verdict the code reached.

# The Kerberos version 4 dialogue, all six messages, and every attack it is built to stop.
import hashlib, hmac, json, random

rng = random.Random(620)

def keystream(key, nonce, n):
    return b''.join(hashlib.sha256(key + nonce + i.to_bytes(4, 'big')).digest()
                    for i in range(n // 32 + 1))[:n]

def E(key, record):                      # seal: encrypt, then MAC (a stand-in for DES)
    nonce = rng.getrandbits(64).to_bytes(8, 'big')
    plain = json.dumps(record, sort_keys=True).encode()
    body = nonce + bytes(p ^ s for p, s in zip(plain, keystream(key, nonce, len(plain))))
    return (body + hmac.new(key, body, hashlib.sha256).digest()).hex()

def D(key, blob):                        # open, or fail loudly
    raw = bytes.fromhex(blob)
    body, tag = raw[:-32], raw[-32:]
    if not hmac.compare_digest(tag, hmac.new(key, body, hashlib.sha256).digest()):
        raise ValueError('integrity check failed')
    nonce, data = body[:8], body[8:]
    return json.loads(bytes(c ^ s for c, s in zip(data, keystream(key, nonce, len(data)))))

def new_key():
    return rng.getrandbits(128).to_bytes(16, 'big')

def clock(t):                            # t is seconds after midnight
    return '%02d:%02d:%02d' % (t // 3600, t // 60 % 60, t % 60)

def say(t, what, result=''):
    print(f'{clock(t)} {what:<44} {result}'.rstrip())

HOUR, SKEW = 3600, 5 * 60                # RFC 4120 s.1.6: "on the order of 5 minutes"
ASHA = '10.0.5.21'
Kc = hashlib.pbkdf2_hmac('sha1', b'monsoon-lab-7', b'COLLEGE.EXAMPLEasha', 4096, 16)
Ktgs = new_key()
Kv = {'files': new_key(), 'print': new_key()}      # each server's own secret key

# ---------------------------------------------------------------- the KDC
def AS(msg1, sender, now):
    Kc_tgs = new_key()
    ticket_tgs = E(Ktgs, {'K': Kc_tgs.hex(), 'IDc': msg1['IDc'], 'ADc': sender,
                          'IDtgs': 'tgs', 'TS2': now, 'Lifetime2': 8 * HOUR})
    return E(Kc, {'K': Kc_tgs.hex(), 'IDtgs': 'tgs', 'TS2': now,
                  'Lifetime2': 8 * HOUR, 'Ticket_tgs': ticket_tgs})

def TGS(msg3, sender, now):
    t = D(Ktgs, msg3['Ticket_tgs'])
    if now >= t['TS2'] + t['Lifetime2']:
        return 'refused: ticket-granting ticket expired'
    a = D(bytes.fromhex(t['K']), msg3['Authenticator'])
    if (a['IDc'], a['ADc']) != (t['IDc'], t['ADc']) or sender != t['ADc']:
        return 'refused: authenticator does not match ticket'
    if abs(now - a['TS3']) > SKEW:
        return 'refused: authenticator outside the clock window'
    Kc_v = new_key()
    life = min(t['TS2'] + t['Lifetime2'] - now, 8 * HOUR)   # never outlives the TGT
    ticket_v = E(Kv[msg3['IDv']], {'K': Kc_v.hex(), 'IDc': t['IDc'], 'ADc': t['ADc'],
                                   'IDv': msg3['IDv'], 'TS4': now, 'Lifetime4': life})
    return E(bytes.fromhex(t['K']), {'K': Kc_v.hex(), 'IDv': msg3['IDv'], 'TS4': now,
                                     'Ticket_v': ticket_v})

# ---------------------------------------------------------------- a server
class Server:
    def __init__(self, key, cache=True):
        self.key, self.cache, self.seen = key, cache, set()

    def serve(self, msg5, sender, now):
        try:
            t = D(self.key, msg5['Ticket_v'])
            a = D(bytes.fromhex(t['K']), msg5['Authenticator'])
        except ValueError as e:
            return 'refused: ' + str(e), None
        if now >= t['TS4'] + t['Lifetime4']:
            return 'refused: ticket expired', None
        if (a['IDc'], a['ADc']) != (t['IDc'], t['ADc']) or sender != t['ADc']:
            return 'refused: authenticator does not match ticket', None
        if abs(now - a['TS5']) > SKEW:
            return 'refused: authenticator outside the clock window', None
        if self.cache:
            if (a['IDc'], a['TS5']) in self.seen:
                return 'refused: authenticator already used', None
            self.seen.add((a['IDc'], a['TS5']))
        return 'accepted: ' + t['IDc'], E(bytes.fromhex(t['K']), {'TS5+1': a['TS5'] + 1})

files = Server(Kv['files'])
wire = []

# ---------------------------------------------------------------- Asha's login
print('THE SIX MESSAGES')
now = 9 * HOUR
msg1 = {'IDc': 'asha', 'IDtgs': 'tgs', 'TS1': now}
say(now, '(1) C -> AS, in clear', msg1)
msg2 = AS(msg1, ASHA, now)
wire.append(msg2)
r2 = D(Kc, msg2)                                       # only her password opens it
Kc_tgs = bytes.fromhex(r2['K'])
say(now, '(2) AS -> C, opened with her password', 'Kc,tgs, lifetime %d h, a sealed TGT'
    % (r2['Lifetime2'] // HOUR))

now += 5
msg3 = {'IDv': 'files', 'Ticket_tgs': r2['Ticket_tgs'],
        'Authenticator': E(Kc_tgs, {'IDc': 'asha', 'ADc': ASHA, 'TS3': now})}
say(now, '(3) C -> TGS: files, the TGT, authenticator')
r4 = D(Kc_tgs, TGS(msg3, ASHA, now))
Kc_v = bytes.fromhex(r4['K'])
say(now, '(4) TGS -> C, opened with Kc,tgs', 'Kc,v and a sealed ticket for files')

now += 1
TS5 = now
msg5 = {'Ticket_v': r4['Ticket_v'],
        'Authenticator': E(Kc_v, {'IDc': 'asha', 'ADc': ASHA, 'TS5': TS5})}
wire.append(msg5)
verdict, msg6 = files.serve(msg5, ASHA, now)
say(now, '(5) C -> V: ticket and authenticator', verdict)
say(now, '(6) V -> C, opened with Kc,v', 'TS5 + 1 = %d, and TS5 was %d'
    % (D(Kc_v, msg6)['TS5+1'], TS5))

# ---------------------------------------------------------------- the attacks
print()
print('RAVI, WHO RECORDED EVERYTHING')
old = wire[-1]
say(now + 34, 'replays message 5 inside the window', files.serve(old, ASHA, now + 34)[0])
say(now + 34, 'the same, to a server that keeps no cache',
    Server(Kv['files'], cache=False).serve(old, ASHA, now + 34)[0])
forged = {'Ticket_v': old['Ticket_v'],
          'Authenticator': E(new_key(), {'IDc': 'asha', 'ADc': ASHA, 'TS5': now + 54})}
say(now + 54, 'writes his own authenticator', files.serve(forged, ASHA, now + 54)[0])
guess = hashlib.pbkdf2_hmac('sha1', b'monsoon', b'COLLEGE.EXAMPLEasha', 4096, 16)
try:
    D(guess, wire[0])
except ValueError as e:
    say(now + 54, 'opens message 2 with the guess "monsoon"', e)
say(now + 414, 'replays message 5 seven minutes later', files.serve(old, ASHA, now + 414)[0])

print()
print('A FAKE FILE SERVER, which does not hold the real server key')
verdict, msg6 = Server(new_key()).serve(msg5, ASHA, now + 84)
say(now + 84, 'tries to open the ticket in message 5', verdict)
say(now + 84, 'so it cannot learn Kc,v or write message 6')

print()
print('ASHA, LATER IN THE DAY')
now = 9 * HOUR + 30 * 60
again = {'Ticket_v': r4['Ticket_v'],
         'Authenticator': E(Kc_v, {'IDc': 'asha', 'ADc': ASHA, 'TS5': now})}
say(now, 'a second connection: message 5 alone', files.serve(again, ASHA, now)[0])
fast = {'Ticket_v': r4['Ticket_v'],
        'Authenticator': E(Kc_v, {'IDc': 'asha', 'ADc': ASHA, 'TS5': now + 60 + 7 * 60})}
say(now + 60, 'her clock runs seven minutes fast', files.serve(fast, ASHA, now + 60)[0])

now = 16 * HOUR
msg3 = {'IDv': 'print', 'Ticket_tgs': r2['Ticket_tgs'],
        'Authenticator': E(Kc_tgs, {'IDc': 'asha', 'ADc': ASHA, 'TS3': now})}
r4p = D(Kc_tgs, TGS(msg3, ASHA, now))
life = D(Kv['print'], r4p['Ticket_v'])['Lifetime4']      # as the print server reads it
say(now, 'a new ticket, for the printer, lives', '%d h: whatever is left of the TGT'
    % (life // HOUR))

now = 17 * HOUR + 30 * 60
msg3 = {'IDv': 'files', 'Ticket_tgs': r2['Ticket_tgs'],
        'Authenticator': E(Kc_tgs, {'IDc': 'asha', 'ADc': ASHA, 'TS3': now})}
say(now, 'eight and a half hours after login', TGS(msg3, ASHA, now))
munotes.in385

The Kerberos Dialogue, Step by Step

THE SIX MESSAGES
09:00:00 (1) C -> AS, in clear                        {'IDc': 'asha', 'IDtgs': 'tgs', 'TS1': 32400}
09:00:00 (2) AS -> C, opened with her password        Kc,tgs, lifetime 8 h, a sealed TGT
09:00:05 (3) C -> TGS: files, the TGT, authenticator
09:00:05 (4) TGS -> C, opened with Kc,tgs             Kc,v and a sealed ticket for files
09:00:06 (5) C -> V: ticket and authenticator         accepted: asha
09:00:06 (6) V -> C, opened with Kc,v                 TS5 + 1 = 32407, and TS5 was 32406

RAVI, WHO RECORDED EVERYTHING
09:00:40 replays message 5 inside the window          refused: authenticator already used
09:00:40 the same, to a server that keeps no cache    accepted: asha
09:01:00 writes his own authenticator                 refused: integrity check failed
09:01:00 opens message 2 with the guess "monsoon"     integrity check failed
09:07:00 replays message 5 seven minutes later        refused: authenticator outside the clock window

A FAKE FILE SERVER, which does not hold the real server key
09:01:30 tries to open the ticket in message 5        refused: integrity check failed
09:01:30 so it cannot learn Kc,v or write message 6

ASHA, LATER IN THE DAY
09:30:00 a second connection: message 5 alone         accepted: asha
09:31:00 her clock runs seven minutes fast            refused: authenticator outside the clock window
16:00:00 a new ticket, for the printer, lives         1 h: whatever is left of the TGT
17:30:00 eight and a half hours after login           refused: ticket-granting ticket expired
munotes.in386

The Kerberos Dialogue, Step by Step

What the run establishes, in order.

The six messages work, and each key opens exactly one thing. Asha's password opens message 2 and nothing else; Kc,tgs opens message 4; Kc,v opens message 6, where she finds 32,407, one more than the 32,406 she put in her authenticator.

A replayed message 5 is refused by a server that keeps a list, and accepted by one that does not. Thirty-four seconds after Asha, Ravi resends her exact message 5. The file server has seen that authenticator and refuses it. The same replay to a server keeping no list is accepted, and Ravi is in as Asha. That second line is MIT's version 4 implementation, and it is why RFC 4120 made the list compulsory.

munotes.in387

The Kerberos Dialogue, Step by Step

A replay outside the window is refused by the clock alone. Seven minutes later the same message fails on its timestamp. The window is what keeps the list short: a server need remember authenticators for only five minutes.

Ravi cannot make an authenticator of his own. He has the ticket, but not Kc,v, so his authenticator fails its integrity check. And his guess at Asha's password does not open message 2.

A fake file server learns nothing. Given a real message 5, it cannot open the ticket without the real server's key, so it cannot learn Kc,v or produce message 6, and Asha, waiting for message 6, stops.

Later in the day, message 5 alone is enough. At 09:30 Asha opens a second connection with a new authenticator and the cached ticket, and the KDC is not involved at all.

A wrong clock locks Asha out. An authenticator seven minutes ahead of the server is refused, exactly as a replay would be. This is the price of timestamps, and it is why clocks are part of Kerberos's security.

Tickets never outlive the login. At 16:00 a new printer ticket is issued for one hour, the remainder of the eight-hour ticket-granting ticket, and at 17:30 the TGS refuses to issue anything: Asha must type her password again.

What each party knows

PartyHolds before the dialogueLearns during itNever learns
Asha's workstationher password, for a momentKc,tgs, Kc,v, the tickets as sealed itemsKtgs, Kv, or what is inside a ticket
ASevery principal's keynothing it did not havethe password itself, only the key derived from it
TGSKtgs and the servers' keysKc,tgs, from the ticketAsha's password
File serverKvKc,v and Asha's name, from the ticketAsha's password, Ktgs
An eavesdroppernothingnames, addresses and times sent in clearany key, or anything sealed

Ticket against authenticator

This is the distinction examiners most often probe, because the two travel together and look alike.

TicketAuthenticator
Made bythe KDC (the AS or the TGS)the client, itself
Encrypted underthe key of the server it is forthe session key the ticket carries
Can the client read itnoyes, it wrote it
Usedmany times, until it expiresonce
Lives forhoursminutes: the clock window
Provesthat the KDC vouches that this session key belongs to this clientthat whoever presents the ticket holds that session key now
munotes.in388

The Kerberos Dialogue, Step by Step

What beginners get wrong here

Saying the password is sent in message 1. Message 1 carries a name, the TGS's name and a time. The password never leaves the workstation.

Saying Kerberos issues the authenticator. The client builds every authenticator itself, from its own name, address and clock, under a session key it already holds.

Putting the wrong key on a message. Message 2 is under Kc, message 4 under Kc,tgs, message 6 under Kc,v. A table of the six messages with the keys swapped is the commonest wrong answer.

Writing message 6 as TS5, or as compulsory. It is TS5 + 1, and it is sent only when the client asks for mutual authentication.

Thinking the TGS keeps a record of the tickets it issued. It does not need to. Each ticket carries its own session key, sealed under the TGS's key, so the TGS can check any ticket with nothing but its own key.

Believing a ticket is used once. The ticket is reused until it expires; the authenticator is used once.

Where version 4's dialogue is weak

These are the next chapter's subject, and they are listed here so that the six messages are not mistaken for flawless.

  • The ticket is encrypted twice on its way to the client, once in the server's key and again inside message 2 or 4, which costs time and buys nothing.
  • Tickets carry an IP address, which ties the protocol to one kind of network.
  • The list of used authenticators was optional, and MIT's own implementation left it out.
  • Message 2 can be attacked offline. Anyone can send message 1 in Asha's name and take the reply away to try passwords against it at leisure.

Quick revision

  • Three exchanges, six messages: AS exchange (1, 2) once per login; TGS exchange (3, 4) once per service; client/server exchange (5, 6) once per session.
  • (1) IDc || IDtgs || TS1, in clear. (2) under Kc: Kc,tgs, IDtgs, TS2, Lifetime2, Ticket_tgs.
  • Ticket_tgs under Ktgs: Kc,tgs, IDc, ADc, IDtgs, TS2, Lifetime2.
  • (3) IDv, Ticket_tgs, authenticator under Kc,tgs. (4) under Kc,tgs: Kc,v, IDv, TS4, Ticket_v.
  • Ticket_v under Kv: Kc,v, IDc, ADc, IDv, TS4, Lifetime4. Authenticator: IDc, ADc, TS, under the session key.
  • (5) Ticket_v and a new authenticator under Kc,v. (6) under Kc,v: TS5 + 1, only for mutual authentication.
  • Ticket: made by the KDC, reusable, hours. Authenticator: made by the client, once, minutes.
  • Service-ticket lifetime is the smaller of the TGT's remainder and the service default. Clock window typically 5 minutes; the replay list is a MUST in version 5.
munotes.in389

The Kerberos Dialogue, Step by Step

Test yourself

1. Write out the six messages of the Kerberos version 4 dialogue, naming the key that protects each reply. (1) C to AS: IDc || IDtgs || TS1, in clear. (2) AS to C: E(Kc, [Kc,tgs || IDtgs || TS2 || Lifetime2 || Ticket_tgs]), under the password-derived key. (3) C to TGS: IDv || Ticket_tgs || Authenticator_c, the authenticator under Kc,tgs. (4) TGS to C: E(Kc,tgs, [Kc,v || IDv || TS4 || Ticket_v]). (5) C to V: Ticket_v || Authenticator_c, the authenticator under Kc,v. (6) V to C: E(Kc,v, [TS5 + 1]). The ticket-granting ticket is sealed under Ktgs and the service ticket under Kv.

2. Why is Asha not asked for her password when she obtains a ticket for the printer in the afternoon? Because the TGS encrypts its reply, message 4, under the session key Kc,tgs that her workstation received in message 2 at login and still holds. Only the reply to message 1 is encrypted under her password-derived key, and that exchange happens once per login.

3. How does the TGS learn the session key Kc,tgs, given that it keeps no record of the tickets issued? The AS puts Kc,tgs inside the ticket-granting ticket, which is sealed under the TGS's own key. When the ticket arrives in message 3, the TGS decrypts it with Ktgs and takes the session key out. Every ticket carries its own session key to the server it is for.

4. Distinguish a ticket from an authenticator. A ticket is made by the KDC, sealed under the key of the server it is for, unreadable by the client, and reusable until it expires, typically hours. An authenticator is made by the client itself from its name, address and the current time, sealed under the session key the ticket carries, and used once within a window of minutes. The ticket proves that the KDC vouches for the client; the authenticator proves that whoever presents the ticket holds its session key now.

5. Why does the server return TS5 + 1 in message 6 rather than TS5? Because TS5 already travelled to the server encrypted under Kc,v inside the authenticator. An impostor could send that ciphertext straight back, and a reply of TS5 would look correct. Adding one forces the replying server to decrypt, change and re-encrypt, which only a holder of Kc,v can do, and only the real server can learn Kc,v, from the ticket sealed under its own key.

munotes.in390

The Kerberos Dialogue, Step by Step

6. An attacker records Asha's message 5 and resends it thirty seconds later. What stops the attack, and what would happen if the server kept no record? The timestamp is still within the clock window, so the timestamp check alone does not stop it. What stops it is the server's list of authenticators already presented, which recognises the repeat. A server keeping no such list would accept the replay and treat the attacker as Asha for that request, which is why RFC 4120 requires every version 5 server to keep the list for the length of the clock window.

7. What is the lifetime of a service ticket issued an hour before the ticket-granting ticket expires, if the service's default is eight hours? One hour. A service ticket's lifetime is the smaller of what remains of the ticket-granting ticket and the service's default, so it can never outlast the login that produced it.

Contents This chapter on its own page

munotes.in391

Chapter Sixty-Three

Kerberos Version 5, Realms and What Changed

Syllabus topic Module 2, "Authentication Applications: Kerberos"

In one line

Version 5 kept version 4's six-message shape and fixed what experience had shown to be wrong with it: one cipher, one kind of network address, a lifetime capped under a day, realms joined in pairs, and a reply anyone could collect and attack offline.

In the words an answer should use: Kerberos version 5, specified in RFC 1510 in 1993 and in RFC 4120 in 2005, addresses the environmental shortcomings of version 4 (dependence on DES and on IP addresses, its byte ordering, its ticket lifetime limit, the lack of authentication forwarding, its short principal names and its pairwise inter-realm keys) and its technical deficiencies (double encryption, the non-standard PCBC mode, weak replay detection, exposure to offline password attacks, session key reuse and a doubtful checksum), while keeping the same authentication, ticket-granting and client/server exchanges.

Why there is a version 5

Version 4 was designed for one campus. When other organisations adopted it they met assumptions that were true at MIT and false elsewhere. The version 5 designers record that work began in 1989, driven by what version 4's users and administrators reported. The protocol was published as RFC 1510 in September 1993 and revised as RFC 4120 in July 2005, which is the specification in force.

The designers themselves grouped version 4's problems into two kinds, and a full answer uses both.

Version 4's environmental shortcomings, and version 5's answer

Environmental means the problem is not a flaw in the cryptography but an assumption about the surroundings that did not travel.

Shortcoming in version 4Why it hurtWhat version 5 does
Encryption system dependence: DES onlyDES could not be exported freely from the United States, and it was ageingEvery ciphertext carries an encryption type tag, and keys carry a type and length, so any cipher can be plugged in
Internet protocol dependence: addresses must be IP addressesunusable on other kinds of networkAddresses are tagged with a type and length, and a ticket may carry several
Message byte ordering: "receiver makes right", each sender uses its own byte ordera machine with an unusual order could not be understoodMessages are defined in ASN.1 and encoded by fixed rules, so the order is part of the standard
Ticket lifetime: an 8-bit count of five-minute unitsthe longest possible ticket is 21.25 hours, too short for long jobsTickets carry a start time and an end time, with no practical ceiling
Authentication forwarding: nonea server could not act for a user, for example a print server fetching the user's fileForwardable and proxiable tickets
Principal naming: three parts of at most 39 characters, no full stop in a namedid not fit existing account namesNames of any number of parts, and the realm kept separate
Inter-realm authentication: every pair of realms must share a keythe number of keys grows with the square of the number of realmsRealms form a hierarchy, and a ticket records the realms it passed through
munotes.in392

Kerberos Version 5, Realms and What Changed

The lifetime ceiling is worth working, because a question can ask for it. Eight bits hold values up to 255. At five minutes a unit, 255 × 5 = 1,275 minutes, and 1,275 / 60 = 21.25 hours, which is 21 hours and 15 minutes.

Version 4's technical deficiencies, and version 5's answer

Technical means a flaw in the protocol or its cryptography.

Deficiency in version 4The problemWhat version 5 does
Double encryptionthe ticket, already sealed under the server's key, is encrypted again inside the reply to the clientthe ticket travels beside the encrypted part of the reply, not inside it
PCBC encryptiona non-standard mode of DES meant to give secrecy and integrity at once, and open to a block-exchange attack that the receiver may not detectstandard CBC with a checksum inside the encrypted data, and later modes with their own integrity check
Authenticators and replay detectionkeeping the list of used authenticators was optional, and MIT's own implementation did notthe replay cache is a MUST (RFC 4120 s.3.2.3)
Password attacksanyone can obtain a reply encrypted under a password-derived key and test guesses against it offlinepre-authentication data, and a salt that differs per realm and per user
Session keysone session key reused across many connections invites replay between thema subsession key per connection, and optional sequence numbers
Cryptographic checksuma checksum whose suitability was unknown in the form MIT implemented itchecksum types are named and tagged, and chosen for being keyed and collision-proof

The six messages in version 5

The shape is version 4's. What changed is visible in the fields: realms appear by name, requests carry options and nonces, lifetimes become times, the ticket carries flags and sits outside the client's encrypted part, and the authenticator can carry a subkey and a sequence number.

(a) Authentication service exchange: KRB_AS_REQ and KRB_AS_REP

  (1)  C -> AS    Options || IDc || Realmc || IDtgs || Times || Nonce1
  (2)  AS -> C    Realmc || IDc || Ticket_tgs ||
                  E(Kc, [Kc,tgs || Times || Nonce1 || Realmtgs || IDtgs])

       Ticket_tgs = E(Ktgs, [Flags || Kc,tgs || Realmc || IDc || ADc || Times])

(b) Ticket-granting service exchange: KRB_TGS_REQ and KRB_TGS_REP

  (3)  C -> TGS   Options || IDv || Times || Nonce2 || Ticket_tgs || Authenticator_c
  (4)  TGS -> C   Realmc || IDc || Ticket_v ||
                  E(Kc,tgs, [Kc,v || Times || Nonce2 || Realmv || IDv])

       Ticket_v        = E(Kv, [Flags || Kc,v || Realmc || IDc || ADc || Times])
       Authenticator_c = E(Kc,tgs, [IDc || Realmc || TS1])

(c) Client/server authentication exchange: KRB_AP_REQ and KRB_AP_REP

  (5)  C -> V     Options || Ticket_v || Authenticator_c
  (6)  V -> C     E(Kc,v, [TS2 || Subkey || Seq#])

       Authenticator_c = E(Kc,v, [IDc || Realmc || TS2 || Subkey || Seq#])
munotes.in393

Kerberos Version 5, Realms and What Changed

Each symbol above corresponds to a named field in RFC 4120's definitions, and knowing the correspondence lets an answer use either vocabulary.

SymbolRFC 4120 fieldWhat it is
Optionskdc-options, ap-optionsflags the client asks for: a forwardable or renewable ticket, mutual authentication
Timesfrom, till, rtime; in tickets authtime, starttime, endtime, renew-tillwhen the ticket starts, ends, and may be renewed until
Nonce1, Nonce2noncea random number the client checks in the reply, so an old reply cannot be replayed to it
Realmc, Realmvcrealm, realmthe client's realm and the server's realm
Flagsflagsthe ticket's properties, listed below
ADccaddrthe client's addresses, now optional and typed
Subkeysubkeya key for this one connection, in place of the reused session key
Seq#seq-numbera starting sequence number for the messages that follow

Four changes deserve a sentence each, because they are the ones a question asks about.

The nonce replaces the timestamp in the requests. The client picks a random number, and the reply must contain it. A reply recorded yesterday cannot be replayed to the client today, because it carries yesterday's nonce.

The ticket is outside the client's encryption. In message 2 the ticket sits beside E(Kc, [...]), not inside it. That removes the double encryption, and the run below counts what it saved.

The server's reply carries the client's own timestamp, not the timestamp plus one. RFC 4120 s.3.2.4 says so and gives the reason: version 5's messages are built so that the reply cannot be made from the authenticator "by judicious message surgery", even in encrypted form, without the key. The reply and the authenticator are different message types with different structures, so one cannot be turned into the other.

The subkey and the sequence number protect the conversation that follows. A fresh key for each connection stops messages from one connection being replayed into another, and sequence numbers let the receiver insist that messages arrive in order and without gaps.

Realms

A realm is one administrative domain of Kerberos: a KDC and the users and servers registered with it. A college is one realm, a neighbouring college another. Realm names usually mirror the organisation's Internet domain name, as MIT's ATHENA.MIT.EDU does.

munotes.in394

Kerberos Version 5, Realms and What Changed

In version 5 a principal's name is written as its parts separated by slashes, then @ and the realm: asha@COLLEGE.EXAMPLE for a user, host/files.college.example@COLLEGE.EXAMPLE for a service. Version 4 wrote name.instance@realm.

For one realm to accept another's users, the two share a key. RFC 4120 s.1.2 describes the arrangement: each realm's TGS is registered as a principal in the other, under an inter-realm key. Asha, registered in one realm, then works through five steps to reach a server in another:

  1. She asks her own TGS for a ticket-granting ticket for the remote realm's TGS.
  2. Her TGS issues it, sealed under the inter-realm key.
  3. She presents it to the remote TGS and asks for a ticket to the remote server.
  4. The remote TGS decrypts it with the inter-realm key, which proves it came from her home realm, and issues the service ticket.
  5. She presents the service ticket to the remote server, as in any client/server exchange.

Version 4 required a key for every pair of realms that wanted to work together, which is n(n - 1)/2 keys for n realms. Version 5 arranges realms in a tree, following their names: each realm shares a key with its parent and with each child, which is n - 1 keys in all, and a ticket for a distant realm is obtained by walking up and down the tree. Heavily used routes may be given a direct shortcut key.

Walking a path creates a trust question: the remote server is told, in effect, that realm A vouches that realm B vouches for Asha. So every version 5 ticket carries a transited field naming every realm the authentication passed through, and RFC 4120 leaves the decision to the server: it "is ultimately responsible for accepting or rejecting authentication and SHOULD check the transited field".

Ticket flags

Version 5 tickets carry flags. Most can be requested; some are set by the KDC alone. The list is RFC 4120's.

FlagMeaning
INITIALissued by the authentication service directly, from the password, not from a ticket-granting ticket. A password-changing service insists on it, so a passer-by at an unattended workstation cannot change the user's password
PRE-AUTHENT, HW-AUTHENTthe client proved itself before the ticket was issued, by pre-authentication or by a hardware device
INVALIDnot yet usable; a server must refuse it until the KDC validates it
RENEWABLEcan be renewed before it expires, up to a second, later limit; the KDC may refuse to renew a ticket reported stolen
MAY-POSTDATE, POSTDATEDa ticket-granting ticket that may be used to obtain a ticket valid only from a future time, and such a ticket, which starts out INVALID; for batch jobs
PROXIABLE, PROXYmay be used to obtain a ticket for another address, with limited rights; for a service acting for the user on one server
FORWARDABLE, FORWARDEDmay be used to obtain a ticket-granting ticket for another address; for a user logging in onward to another machine
TRANSITED-POLICY-CHECKEDthe KDC has checked the transited realms against policy
OK-AS-DELEGATEthe server is trusted by its realm to receive delegated credentials
munotes.in395

Kerberos Version 5, Realms and What Changed

Renewal is the answer to the lifetime trade the first Kerberos chapter described. A ticket valid for eight hours but renewable for a week limits a thief to eight hours unless they can also get past the KDC's list of stolen tickets, while a long-running job simply renews in time.

Pre-authentication, and what it does not fix

In version 4, message 1 is answered for anyone who asks. An attacker sends it in Asha's name, takes the reply home, and tries passwords against it at leisure. Nobody is alerted, because nothing is sent to the KDC after the first request. The reply contains predictable content, so the attacker knows when a guess is right.

Version 5 adds pre-authentication. With the common method, PA-ENC-TIMESTAMP, the client includes in message 1 the current time encrypted under its password-derived key. A KDC that requires pre-authentication answers a request without it with the error KDC_ERR_PREAUTH_REQUIRED, and answers a request with it only if the timestamp decrypts and is current.

This stops the attacker from collecting replies simply by asking. It does not stop an attacker who overhears a real login, because the encrypted timestamp in that login is itself something to test guesses against. The version 5 designers were precise about this: pre-authentication "makes it a little more difficult" for an attacker to obtain something to test guesses against. The defence against guessing is a password that is not in anybody's list, and a slow key derivation that makes every guess cost something.

The run

The listing works the lifetime ceiling, counts inter-realm keys, runs the guessing attack four ways, and measures the double encryption. The password-to-key step is RFC 3962's PBKDF2 at its default 4,096 rounds; sealing is encrypt-then-MAC, a stand-in for Kerberos's ciphers.

# Kerberos version 5 against version 4: the changes counted, and the guessing attack run.
import hashlib, hmac, json, random

rng = random.Random(630)

def keystream(key, nonce, n):
    return b''.join(hashlib.sha256(key + nonce + i.to_bytes(4, 'big')).digest()
                    for i in range(n // 32 + 1))[:n]

def E(key, record):                       # encrypt, then MAC
    nonce = rng.getrandbits(64).to_bytes(8, 'big')
    plain = json.dumps(record, sort_keys=True).encode()
    body = nonce + bytes(p ^ s for p, s in zip(plain, keystream(key, nonce, len(plain))))
    return (body + hmac.new(key, body, hashlib.sha256).digest()).hex()

def D(key, blob):
    raw = bytes.fromhex(blob)
    body, tag = raw[:-32], raw[-32:]
    if not hmac.compare_digest(tag, hmac.new(key, body, hashlib.sha256).digest()):
        raise ValueError('integrity check failed')
    nonce, data = body[:8], body[8:]
    return json.loads(bytes(c ^ s for c, s in zip(data, keystream(key, nonce, len(data)))))

def string_to_key(password, salt, rounds=4096):   # RFC 3962's PBKDF2 step
    return hashlib.pbkdf2_hmac('sha1', password.encode(), salt.encode(), rounds, 16)

# ---- 1. version 4's longest ticket --------------------------------------------
units = 2 ** 8 - 1                          # the largest value an 8-bit field holds
print('VERSION 4 LIFETIME FIELD: 8 bits, counting five-minute units')
print('  largest value', units, 'units =', units * 5, 'minutes =', units * 5 / 60, 'hours')
print('  version 5 stores a start time and an end time instead, with no such ceiling')

# ---- 2. joining realms ----------------------------------------------------------
print()
print('KEYS NEEDED TO JOIN n REALMS')
print('  realms   every pair shares a key (v4)   a tree of realms (v5)')
for n in (3, 10, 50, 200):
    print(f'  {n:>6}   {n * (n - 1) // 2:>28,}   {n - 1:>21,}')

# ---- 3. guessing a password offline ---------------------------------------------
print()
print('OFFLINE GUESSING AGAINST THE REPLY TO MESSAGE 1')
REALM = 'COLLEGE.EXAMPLE'
wordlist = ['123456', 'password', 'qwerty', 'india@123', 'iloveyou', 'admin123',
            'welcome', 'welcome123', 'college2026', 'sunshine', 'mumbai123']
accounts = {'karan': 'welcome123', 'meera': 'Tq7!vineyard-bus-42'}
Ktgs = rng.getrandbits(128).to_bytes(16, 'big')

def as_reply(user, now):                    # the part of message 2 under the user's key
    Kc = string_to_key(accounts[user], REALM + user)
    return E(Kc, {'K': rng.getrandbits(128).to_bytes(16, 'big').hex(),
                  'nonce': rng.getrandbits(32), 'sname': 'krbtgt/' + REALM, 'end': now + 36000})

def kdc(request, preauth_required, now):
    user = request['cname']
    if preauth_required:
        if 'padata' not in request:
            return 'KDC_ERR_PREAUTH_REQUIRED'
        stamp = D(string_to_key(accounts[user], REALM + user), request['padata'])
        if abs(now - stamp['patimestamp']) > 300:
            return 'KDC_ERR_PREAUTH_FAILED'
    return as_reply(user, now)

def guess(blob, user):
    for tries, word in enumerate(wordlist, 1):
        try:
            D(string_to_key(word, REALM + user), blob)
            return f'found "{word}", guess {tries} of {len(wordlist)}'
        except ValueError:
            pass
    return f'not found in {len(wordlist)} guesses'

now = 32400
for label, needs_preauth in (('version 4: anyone may ask', False),
                             ('version 5, no pre-authentication', False),
                             ('version 5, pre-authentication', True)):
    reply = kdc({'cname': 'karan'}, needs_preauth, now)
    if reply.startswith('KDC_ERR'):
        print(f'  {label:<35} {reply}: no reply to attack')
    else:
        print(f'  {label:<35} {guess(reply, "karan")}')

Kk = string_to_key('welcome123', REALM + 'karan')
recorded = E(Kk, {'patimestamp': now, 'pausec': 0})      # karan's own real login, overheard
print(f'  {"version 5, a real login overheard":<35} {guess(recorded, "karan")}')
reply = kdc({'cname': 'meera'}, False, now)
print(f'  {"a strong password, any version":<35} {guess(reply, "meera")}')
print(f'  each guess costs 4,096 PBKDF2 rounds, so eight cost {8 * 4096:,}')

# ---- 4. the double encryption ---------------------------------------------------
print()
print('BYTES PUT THROUGH THE CIPHER TO BUILD MESSAGE 2')
Kc = string_to_key('welcome123', REALM + 'karan')
ticket_fields = json.dumps({'flags': 'initial', 'key': '00' * 16, 'crealm': REALM,
                            'cname': 'karan', 'caddr': '10.0.5.21', 'authtime': now,
                            'endtime': now + 36000}).encode()
reply_fields = json.dumps({'key': '00' * 16, 'nonce': 7, 'sname': 'krbtgt/' + REALM,
                           'endtime': now + 36000}).encode()
sealed_ticket = len(ticket_fields) + 8 + 32         # plus the nonce and the MAC tag
v5 = len(ticket_fields) + len(reply_fields)
v4 = len(ticket_fields) + len(reply_fields) + sealed_ticket
print(f'  the ticket\'s fields {len(ticket_fields)} bytes, the reply\'s own fields'
      f' {len(reply_fields)} bytes')
print(f'  version 5, ticket beside the reply   {v5:>4} bytes enciphered')
print(f'  version 4, ticket inside the reply   {v4:>4} bytes enciphered')
print(f'  the extra {v4 - v5} bytes are the sealed ticket, enciphered a second time:'
      f' {100 * (v4 - v5) / v5:.0f} per cent more work')
munotes.in396

Kerberos Version 5, Realms and What Changed

VERSION 4 LIFETIME FIELD: 8 bits, counting five-minute units
  largest value 255 units = 1275 minutes = 21.25 hours
  version 5 stores a start time and an end time instead, with no such ceiling

KEYS NEEDED TO JOIN n REALMS
  realms   every pair shares a key (v4)   a tree of realms (v5)
       3                              3                       2
      10                             45                       9
      50                          1,225                      49
     200                         19,900                     199

OFFLINE GUESSING AGAINST THE REPLY TO MESSAGE 1
  version 4: anyone may ask           found "welcome123", guess 8 of 11
  version 5, no pre-authentication    found "welcome123", guess 8 of 11
  version 5, pre-authentication       KDC_ERR_PREAUTH_REQUIRED: no reply to attack
  version 5, a real login overheard   found "welcome123", guess 8 of 11
  a strong password, any version      not found in 11 guesses
  each guess costs 4,096 PBKDF2 rounds, so eight cost 32,768

BYTES PUT THROUGH THE CIPHER TO BUILD MESSAGE 2
  the ticket's fields 169 bytes, the reply's own fields 108 bytes
  version 5, ticket beside the reply    277 bytes enciphered
  version 4, ticket inside the reply    486 bytes enciphered
  the extra 209 bytes are the sealed ticket, enciphered a second time: 75 per cent more work
munotes.in397

Kerberos Version 5, Realms and What Changed

What the run establishes, in order.

Version 4's lifetime ceiling is 21.25 hours, from an 8-bit field counting five-minute units. Version 5 has no such field.

Pairwise realm keys do not scale. Fifty realms need 1,225 shared keys in pairs and 49 in a tree; two hundred need 19,900 against 199.

Version 4 hands an attacker something to attack. The attacker asks in Karan's name, receives the reply, and his weak password welcome123, eighth in an eleven-word list of common choices, falls offline. Version 5 without pre-authentication is exactly as exposed. With pre-authentication required, asking yields only an error and nothing to test.

But pre-authentication does not save a weak password from an eavesdropper. Karan's own encrypted timestamp, overheard during a real login, falls to the same eighth guess. A strong password survives every version, because it is not in the list. Each guess costs 4,096 PBKDF2 rounds, which slows an attacker down and does nothing to stop them if the password is common.

munotes.in398

Kerberos Version 5, Realms and What Changed

Double encryption was pure waste. Building message 2 in version 4 puts the sealed ticket through the cipher a second time, 75 per cent more work than version 5 does for the same message.

Where Kerberos is today

Version 5 is the Kerberos in use, and its ciphers have moved on twice. AES was added for Kerberos by RFC 3962 in February 2005. RFC 6649, July 2012, says Kerberos implementations and deployments SHOULD NOT implement or deploy DES, noting that by 2008 commercial hardware costing under 15,000 US dollars could break a DES key in less than a day on average. RFC 8429, October 2018, deprecates Triple DES and RC4 in Kerberos in the same terms.

Every Windows domain controller is a Kerberos KDC. Microsoft's documentation, updated in September 2026, states that Windows Server implements the Kerberos version 5 protocol, that the KDC runs on every domain controller, and that it uses the domain's Active Directory database as its account database. The same page contrasts Kerberos with the older NTLM on exactly the point the previous chapter made with message 6: NTLM does not let a client verify a server's identity, and Kerberos does.

The attacks that matter now are version 4's password attack in modern form. MITRE ATT&CK, the public catalogue of attackers' techniques, lists two:

  • AS-REP roasting (T1558.004): accounts with pre-authentication switched off answer an unauthenticated request with a reply that can be taken away and cracked offline, exactly as in version 4.
  • Kerberoasting (T1558.003): any user holding a ticket-granting ticket may request service tickets, which are sealed under the service account's key; where that key comes from a password, and especially where the ticket uses RC4, the ticket can be cracked offline.

The defences follow from the run: keep pre-authentication on, give service accounts long random passwords, and stop using RC4, which RFC 8429 already says.

Version 4 against version 5

Version 4Version 5
Specified inthe 1988 USENIX paper and MIT's implementationRFC 1510 (1993), then RFC 4120 (2005)
CiphersDES only, in PCBC modeany, tagged by type; AES today; DES, 3DES, RC4 deprecated
Encodingreceiver makes rightASN.1, fixed encoding rules
AddressesIP only, onetyped, several, optional
Lifetime8-bit count of five-minute units, at most 21.25 hoursstart and end times; renewable and postdated tickets
Ticket in message 2inside the client's encryptionbeside it
Server's replyTS5 + 1the client's own timestamp; message formats prevent reflection
Replay cacheoptional; absent in MIT's implementationMUST
Password attack on message 2open to anyonepre-authentication available
Realmsa key for every pairhierarchy, transited field
Forwarding and proxiesnoneFORWARDABLE, PROXIABLE
Namesname.instance@realm, 39 characters eachany number of parts, realm separate
munotes.in399

Kerberos Version 5, Realms and What Changed

What beginners get wrong here

Saying version 5 is a different protocol. It is the same three exchanges and six messages. The encoding, the fields and the options changed; the design did not.

Saying pre-authentication ends password guessing. It stops guessing against replies obtained by asking. A recorded real login can still be attacked offline, and a weak password still falls.

Calling the realm a server. A realm is an administrative domain, a KDC together with every principal registered with it.

Keeping TS5 + 1 in a version 5 answer. Version 5 returns the client's own timestamp in the reply, and RFC 4120 says why the addition is no longer needed.

Quoting 21 hours as a version 5 limit. It is version 4's ceiling, from its 8-bit lifetime field. Version 5 tickets carry times.

Quick revision

  • Version 5: work began 1989; RFC 1510 (1993); RFC 4120 (July 2005). Same six messages; message names KRB_AS_REQ, KRB_AS_REP, KRB_TGS_REQ, KRB_TGS_REP, KRB_AP_REQ, KRB_AP_REP.
  • Environmental shortcomings of version 4: DES only; IP only; byte order; lifetime 255 × 5 = 1,275 minutes, 21.25 hours; no forwarding; short names; pairwise realm keys.
  • Technical deficiencies: double encryption; PCBC; optional replay list; offline password attack; session key reuse; doubtful checksum.
  • Version 5 adds encryption types, ASN.1, start and end times, flags, nonces, the ticket outside the client's encryption, subkeys and sequence numbers, pre-authentication, a mandatory replay cache.
  • Realms: inter-realm keys; version 4 needs n(n - 1)/2, a version 5 tree n - 1; the transited field lets the server decide whom to trust.
  • Flags: INITIAL, PRE-AUTHENT, HW-AUTHENT, INVALID, RENEWABLE, MAY-POSTDATE, POSTDATED, PROXIABLE, PROXY, FORWARDABLE, FORWARDED, TRANSITED-POLICY-CHECKED, OK-AS-DELEGATE.
  • Today: AES (RFC 3962); DES deprecated (RFC 6649, 2012); 3DES and RC4 deprecated (RFC 8429, 2018); Kerberos version 5 in every Windows domain; AS-REP roasting and Kerberoasting are offline password attacks.

Test yourself

1. List the environmental shortcomings of Kerberos version 4. Dependence on DES as its only cipher; dependence on IP addresses; its "receiver makes right" byte ordering; its ticket lifetime, an 8-bit count of five-minute units with a ceiling of 21.25 hours; no provision for forwarding a user's credentials to another host; principal names of three parts of at most 39 characters each; and inter-realm authentication that needs a separate key for every pair of realms.

2. What is the double encryption deficiency, and how does version 5 remove it? In version 4 the ticket, already encrypted under the server's key, is placed inside the reply to the client and encrypted a second time under the client's key, which costs processing and adds no security, because the client could not read the ticket anyway. In version 5 the ticket is carried beside the encrypted part of the reply rather than inside it.

munotes.in400

Kerberos Version 5, Realms and What Changed

3. Calculate the longest ticket lifetime version 4 allows, and say why version 5 has no such limit. The lifetime is an 8-bit field counting five-minute units. Its largest value is 255, so the longest lifetime is 255 × 5 = 1,275 minutes, which is 21.25 hours. Version 5 records a ticket's start time and end time directly instead of a count, so no field width limits it.

4. How does pre-authentication work, and what attack does it not prevent? The client includes in its first request the current time encrypted under its password-derived key. A KDC that requires pre-authentication refuses to answer a request without it, so an attacker can no longer obtain a reply to attack simply by asking in a user's name. It does not prevent an attacker who overhears a real login from testing password guesses offline against the encrypted timestamp in it, so a weak password remains vulnerable.

5. How many keys are needed to join ten realms under version 4's scheme, and under a version 5 hierarchy? Version 4 needs a key for each pair: 10 × 9 / 2 = 45 keys. A version 5 tree needs one for each parent and child link, which is 10 - 1 = 9 keys.

6. What is the transited field, and who decides whether to trust it? It is a field in every version 5 ticket listing the realms through which the client's authentication passed on its way to the server's realm. The application server decides whether to accept an authentication that passed through those realms, and RFC 4120 says it should check the field.

7. Why does the version 5 server not add one to the timestamp in its reply? Because in version 5 the reply and the authenticator are different message types with different structures, so an attacker cannot build the reply from the authenticator by rearranging ciphertext without knowing the key. RFC 4120 notes that version 4 added one to the client's timestamp and that this is unnecessary in version 5.

Contents This chapter on its own page

munotes.in401

Chapter Sixty-Four

X.509: The Certificate and Its Fields

Syllabus topic Module 2, "Authentication Applications: X.509 Authentication"

In one line

An X.509 certificate is a small signed document that says "this public key belongs to this name", signed by an authority whose own public key you already trust.

In the words an answer should use: an X.509 certificate is a data structure, defined by ITU-T Recommendation X.509 and profiled for the Internet by RFC 5280, that binds a subject's name to a public key for a stated validity period; it is issued and digitally signed by a certification authority (CA), so that anyone holding the CA's public key can verify that the binding was made by that CA and has not been altered.

Why certificates exist

The key-distribution chapter of Module 1 ended on a problem: a public key is only useful if you know whose it is. Anyone can generate a key pair and announce "this is the bank's key". A certificate moves the question from "is this the bank's key?" to "did an authority I already trust say so?", and the second question can be answered by checking one signature.

The same chapter set out what a certificate scheme must allow: anyone can read a certificate and learn the owner's name and key; anyone can verify that it came from the CA and is not counterfeit; only the CA can create or change one; and anyone can check that it is still current. X.509 is the format that meets those four requirements, and it is the one the whole Internet uses.

Where X.509 comes from

X.509 is an ITU-T Recommendation, published jointly with ISO as ISO/IEC 9594-8. It was first published in 1988 as part of the X.500 series of Recommendations on directory services, and it defined the certificate format that the rest of the world then adopted. The Recommendation's own history table, and RFC 5280's account of it, give the dates:

YearWhat changed
1988first edition; the certificate format of this edition is version 1
1993second edition; two optional fields added, the issuer and subject unique identifiers, giving version 2
1996the version 3 format completed (June 1996), adding extensions, and published in the 1997 edition
2008RFC 5280 profiles version 3 for the Internet: which fields and extensions Internet software must support
2019the current edition, approved in October 2019 and published as ISO/IEC 9594-8:2020

Version 3 is the only one worth learning in detail. RFC 5280 says that when extensions are used, "as expected in this profile, version MUST be 3". Every certificate a browser sees is version 3.

The certificate's three parts

At the top level a certificate has exactly three parts, and seeing them is most of understanding what a CA does:

  1. tbsCertificate, "to be signed": every field that says something, the name, the key, the dates, the extensions.
  2. signatureAlgorithm: which algorithm the CA used to sign.
  3. signatureValue: the CA's signature on the tbsCertificate bytes, and on nothing else.
munotes.in402

X.509: The Certificate and Its Fields

The signature covers the first part only. The CA hashes the encoded tbsCertificate and signs the hash with its private key. A verifier hashes the same bytes and checks the signature with the CA's public key. Change one byte of the first part and the check fails.

Every field, and what it is for

The fields of tbsCertificate, in order, as RFC 5280 s.4.1 defines them. The last column is the value in the certificate munotes.in served on 30 September 2026, read by the listing below.

FieldWhat it holdsWhy it is theremunotes.in's value
version1, 2 or 3, stored as 0, 1 or 2tells software which fields to expect3
serialNumbera positive integer, unique for this CA, at most 20 bytesissuer name plus serial number identifies exactly one certificate; revocation lists name certificates by it18 bytes
signaturethe algorithm the CA usedmust equal the outer signatureAlgorithmecdsa-with-SHA384
issuerthe CA's distinguished namesays whose public key verifies the signatureLet's Encrypt, YE1
validitynotBefore and notAfterthe period in which the CA warrants it will keep status information about the certificate12 September to 11 December 2026
subjectthe owner's distinguished namethe name being bound to the keyCN=munotes.in
subjectPublicKeyInfothe algorithm and the public key itselfthe whole point of the certificatean elliptic-curve key on P-256
issuerUniqueID, subjectUniqueIDoptional bit strings (version 2 and 3)to tell apart two issuers or subjects with the same nameabsent
extensionsa list of extra fields (version 3 only)everything the first seven fields cannot sayten of them

A distinguished name is a name built from typed parts: country C, organisation O, common name CN, and others. C=US, O=Let's Encrypt, CN=YE1 is one.

Three details carry marks.

The validity period includes both ends. RFC 5280 says the period runs "from notBefore through notAfter, inclusive". munotes.in's certificate runs from 11:54:59 on 12 September to 11:54:58 on 11 December, which is 89 days, 23 hours, 59 minutes and 59 seconds apart, and so exactly 90 days counting both ends.

Dates are written in two forms. Up to 2049 a date is encoded as UTCTime, with a two-digit year; from 2050 it must be GeneralizedTime, with four. A certificate with no well-defined expiry date uses the notAfter value 99991231235959Z.

The issuer and serial number together name one certificate. That is why a revocation list, in the next chapter, is a list of serial numbers.

munotes.in403

X.509: The Certificate and Its Fields

The textbook's notation

The prescribed textbook writes certificates in a compact notation, which is worth being able to read and to write.

  • Y<<X>> means the certificate of user X issued by CA Y.
  • Y{I} means the information I, signed by Y: I together with Y's signature on it.
  • So a certificate of user A issued by a CA is written
CA<<A>> = CA {V, SN, AI, CA, UCA, A, UA, Ap, TA}

where V is the version, SN the serial number, AI the signature algorithm identifier, CA the issuer's name, UCA the issuer's unique identifier, A the subject's name, UA the subject's unique identifier, Ap A's public key, and TA the validity period. The list is the tbsCertificate fields in order, and the braces say the CA signed them.

The 2019 Recommendation writes a signed item the same way, A{...} for information signed by A, and writes Ap for A's public key and Bp[...] for something encrypted under B's public key, which is the notation of the authentication procedures below.

Extensions

Version 3 added extensions, and every modern certificate depends on them. Each extension is three things: an object identifier naming it, a flag saying whether it is critical, and its value.

The critical flag is a rule about unknown extensions. RFC 5280 s.4.2: software "MUST reject the certificate if it encounters a critical extension it does not recognize"; a non-critical extension "MAY be ignored if it is not recognized, but MUST be processed if it is recognized". So a CA marks critical exactly the restrictions it cannot afford to have ignored.

X.509 itself sorts the standard extensions into groups, and the ones in munotes.in's certificate fall into all three:

Key and policy information.

  • Key usage (critical in both certificates below): what the key may be used for. munotes.in's key may make digital signatures only; the CA's key may also sign certificates (keyCertSign) and revocation lists (cRLSign).
  • Extended key usage: finer purposes by name. serverAuth means a TLS server.
  • Subject key identifier and authority key identifier: short fingerprints of the subject's key and of the issuer's key. They let software find the issuer's certificate when a CA has more than one key: munotes.in's authority key identifier equals YE1's subject key identifier, and the run checks it.
  • Certificate policies: which published rules the CA followed when it issued. 2.23.140.1.2.1 is the CA/Browser Forum's "domain validated" policy: the CA checked only that the applicant controls the domain.

Subject and issuer information.

  • Subject alternative name: the names the key really belongs to. For a website this is the list of host names, here munotes.in and www.munotes.in. This, not the subject's common name, is what a browser matches. RFC 9525, November 2023, is blunt: the Common Name "MUST NOT be used to identify a service", because it is free-form text.
munotes.in404

X.509: The Certificate and Its Fields

Certification path constraints.

  • Basic constraints (critical): whether the subject is a CA. munotes.in's says CA: no, so its key must never be accepted as the signer of another certificate. YE1's says CA: yes, path length 0: it may sign certificates, but no further CA may sit below it. RFC 5280 requires this extension, marked critical, in every CA certificate whose key signs certificates.

And three that tell software where to look:

  • Authority information access: where to fetch the issuer's certificate, http://ye1.i.lencr.org/.
  • CRL distribution points: where to fetch the revocation list that would name this certificate if it were revoked, the subject of the next chapter.
  • Certificate Transparency timestamps: signed promises from public logs that this certificate will be added to them within a fixed time (RFC 6962), so that a certificate issued by mistake, or by a CA that has been attacked, cannot be issued in secret. munotes.in's carries two.

The run: reading munotes.in's certificate

The two files below are real certificates as munotes.in served them on 30 September 2026: its own, and its issuer's. A certificate on disk is usually PEM, base64 text between two marker lines; underneath is DER, the binary encoding of the ASN.1 structure RFC 5280 defines, where every item is a tag, a length and a value.

-----BEGIN CERTIFICATE-----
MIIDkjCCAxmgAwIBAgISBf++q0njvGQyUykj6Cj+EYiQMAoGCCqGSM49BAMDMDMx
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNZ
RTEwHhcNMjYwOTEyMTE1NDU5WhcNMjYxMjExMTE1NDU4WjAVMRMwEQYDVQQDEwpt
dW5vdGVzLmluMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtlAU0VbQfEk4HTrX
5hIWuUpiQ20GHhNVuqE+ZDVD9ULSrOf4Kg8qSn0gtLQPRk5fVBf/1lUus8ioEKMA
V2+Tt6OCAikwggIlMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcD
ATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTYT4mXaBFZ9hSOXEPu3Mg0+5NmNTAf
BgNVHSMEGDAWgBS7IMpHC/7X5Zz5jwkqo4w3RbG82DAzBggrBgEFBQcBAQQnMCUw
IwYIKwYBBQUHMAKGF2h0dHA6Ly95ZTEuaS5sZW5jci5vcmcvMCUGA1UdEQQeMByC
Cm11bm90ZXMuaW6CDnd3dy5tdW5vdGVzLmluMBMGA1UdIAQMMAowCAYGZ4EMAQIB
MC4GA1UdHwQnMCUwI6AhoB+GHWh0dHA6Ly95ZTEuYy5sZW5jci5vcmcvMTcuY3Js
MIIBDQYKKwYBBAHWeQIEAgSB/gSB+wD5AHYA1219ENGn9XfCx+lf1wC/+YLJM1pl
4dCzAXMXwMjFaXcAAAGgla4X3QAABAMARzBFAiBCYhMg08b9CrBS19uyuz3wZStw
W7DE7t8ZSDPcUvBD4gIhAMD7yhJN64KP6bWG5H6ZA68VLvNZCiRLQrsvnH92e8bO
AH8AqCbL4wrGNRJGUz/gZfFPGdluGQgTxB3ZbXkAsxI8VScAAAGgla4azQAIAAAF
ADH5KHsEAwBIMEYCIQCQybl79cSWejo8WdCZ3O4BS7uIG/gVa+gwzM4PHheFCgIh
AIgzHibZCL4C2UUgOwU0rHkIHnhr05PVx6KajQca6AZiMAoGCCqGSM49BAMDA2cA
MGQCMAR9Skrz1wHkFWwr/d/D28Pjlo8+ZPALlN7CuZG728/ZJy+h/Wn68R5HhziI
YSN6OwIwRID/awRkE2KqxXBIPSQ+zj2S4d/UivmhZt2GHve0KRjTiuh1JUdx4iH1
MqrwigZp
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICizCCAhGgAwIBAgIQXd1w3TH4AchcGGp6BLgK/jAKBggqhkjOPQQDAzAuMQsw
CQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZRTAeFw0y
NTA5MDMwMDAwMDBaFw0yODA5MDIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYwFAYD
VQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNZRTEwdjAQBgcqhkjOPQIBBgUr
gQQAIgNiAAQHZVB1/mimla2hfSurylScjPMZaOJXLz/NnAc2sylm8WDyhU9Ccp+z
ASQi5vSwGGJjSGklkD9fdPR8GpyDIOIjCEfrnbt/v+ZSEPLLEGbaM6EccDbN7p9x
teIm2Avf+ryjge4wgeswDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUF
BwMBMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLsgykcL/tflnPmPCSqj
jDdFsbzYMB8GA1UdIwQYMBaAFKPIJlqOoUzQNWP8myPIOq5W809WMDIGCCsGAQUF
BwEBBCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3llLmkubGVuY3Iub3JnLzATBgNV
HSAEDDAKMAgGBmeBDAECATAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8veWUuYy5s
ZW5jci5vcmcvMAoGCCqGSM49BAMDA2gAMGUCMQDgjUEahFT/h3DRakqiPZpLvPgf
Zwkt6K2EOMmh1nvEzl83eMLYcod4GCl3b0J1Nn0CMBNYmEQJb4CEG5WoOe7aRn/L
VKu6saHmHEynI7ysIPd8zQsK1HdmhlHKlw9Z5GpGvA==
-----END CERTIFICATE-----

The listing reads the DER with a forty-line reader, prints every field and extension, and then checks the CA's signature. The signature is ECDSA on the curve P-384, the elliptic-curve scheme of the signature standard chapters; the curve's constants are taken from FIPS 186-4 and checked before use.

# Every field of a real certificate, read from its bytes, and the CA's signature checked.
import base64, datetime, hashlib

def der_of(path):                         # PEM is base64 between two marker lines
    lines = open(path).read().split('\n')
    return base64.b64decode(''.join(l for l in lines if l and not l.startswith('-----')))

def tlv(d, i):                            # one DER element: (tag, value start, value end)
    tag, n, j = d[i], d[i + 1], i + 2
    if n & 0x80:                          # long form: the next (n - 128) bytes hold the length
        k = n & 0x7F
        n, j = int.from_bytes(d[j:j + k], 'big'), j + k
    return tag, j, j + n

def items(d, start, end):                 # the elements inside a SEQUENCE, SET or wrapper
    out, i = [], start
    while i < end:
        tag, s, e = tlv(d, i)
        out.append((tag, s, e, i))        # i is where the whole element begins
        i = e
    return out

def oid(b):
    arcs, v = [b[0] // 40, b[0] % 40], 0
    for byte in b[1:]:
        v = (v << 7) | (byte & 0x7F)
        if not byte & 0x80:
            arcs.append(v)
            v = 0
    return '.'.join(map(str, arcs))

NAMES = {'1.2.840.10045.4.3.3': 'ecdsa-with-SHA384', '1.2.840.10045.2.1': 'EC public key',
         '1.2.840.10045.3.1.7': 'P-256', '1.3.132.0.34': 'P-384', '2.5.4.3': 'CN',
         '2.5.4.6': 'C', '2.5.4.10': 'O', '2.5.29.15': 'keyUsage', '2.5.29.37': 'extKeyUsage',
         '2.5.29.19': 'basicConstraints', '2.5.29.14': 'subjectKeyIdentifier',
         '2.5.29.35': 'authorityKeyIdentifier', '1.3.6.1.5.5.7.1.1': 'authorityInfoAccess',
         '2.5.29.17': 'subjectAltName', '2.5.29.32': 'certificatePolicies',
         '2.5.29.31': 'cRLDistributionPoints', '1.3.6.1.4.1.11129.2.4.2': 'CT timestamps',
         '1.3.6.1.5.5.7.3.1': 'serverAuth', '2.23.140.1.2.1': 'domain validated'}
USAGE = ['digitalSignature', 'nonRepudiation', 'keyEncipherment', 'dataEncipherment',
         'keyAgreement', 'keyCertSign', 'cRLSign', 'encipherOnly', 'decipherOnly']

def name(d, s, e):                        # SEQUENCE of SET of (type, string)
    parts = []
    for _, s1, e1, _ in items(d, s, e):
        for _, s2, e2, _ in items(d, s1, e1):
            (_, a, b, _), (_, c, f, _) = items(d, s2, e2)
            parts.append(NAMES.get(oid(d[a:b]), oid(d[a:b])) + '=' + d[c:f].decode())
    return ', '.join(parts)

def when(d, s, e):
    return datetime.datetime.strptime(d[s:e].decode(), '%y%m%d%H%M%SZ')

def uris(d, s, e, tag=0x86):              # every [6] URI or [2] dNSName, however deep
    found = []
    for t, s1, e1, _ in items(d, s, e):
        if t == tag:
            found.append(d[s1:e1].decode())
        elif t & 0x20:                    # constructed: look inside
            found += uris(d, s1, e1, tag)
    return found

def extension(d, key, s, e):
    if key == 'keyUsage':
        _, a, b = tlv(d, s)
        bits = int.from_bytes(d[a + 1:b], 'big') << 8 >> 8
        width = (b - a - 1) * 8
        return ' '.join(u for k, u in enumerate(USAGE) if k < width and bits >> (width - 1 - k) & 1)
    if key == 'basicConstraints':
        inner = items(d, *tlv(d, s)[1:])
        ca = bool(inner and inner[0][0] == 0x01 and d[inner[0][1]] != 0)
        path = [int.from_bytes(d[a:b], 'big') for t, a, b, _ in inner if t == 0x02]
        return 'CA: ' + ('yes' if ca else 'no') + (', path length %d' % path[0] if path else '')
    if key == 'extKeyUsage' or key == 'certificatePolicies':
        found, stack = [], [tlv(d, s)[1:]]
        while stack:
            a, b = stack.pop()
            for t, s1, e1, _ in items(d, a, b):
                if t == 0x06:
                    o = oid(d[s1:e1]); found.append(NAMES.get(o, o))
                elif t == 0x30:
                    stack.append((s1, e1))
        return ', '.join(found)
    if key == 'subjectKeyIdentifier':
        _, a, b = tlv(d, s)
        return d[a:b].hex().upper()[:16] + '...'
    if key == 'authorityKeyIdentifier':
        _, a, b = tlv(d, s)
        return d[items(d, a, b)[0][1]:items(d, a, b)[0][2]].hex().upper()[:16] + '...'
    if key == 'subjectAltName':
        return ', '.join(uris(d, *tlv(d, s)[1:], tag=0x82))
    if key in ('authorityInfoAccess', 'cRLDistributionPoints'):
        return ', '.join(uris(d, *tlv(d, s)[1:]))
    if key == 'CT timestamps':
        return '%d signed timestamps from public logs' % len(scts(d[s:e]))
    return '(%d bytes)' % (e - s)

def scts(blob):                           # TLS-encoded list inside an OCTET STRING
    _, a, b = tlv(blob, 0)
    body, found, i = blob[a + 2:b], [], 0
    while i < len(body):
        n = int.from_bytes(body[i:i + 2], 'big')
        found.append(body[i + 2:i + 2 + n]); i += 2 + n
    return found

def read(path):
    d = der_of(path)
    _, s, e = tlv(d, 0)
    (_, ts, te, tstart), (_, as_, ae, _), (_, ss, se, _) = items(d, s, e)
    f = items(d, ts, te)
    cert = {'der': d, 'tbs': (tstart, te), 'fields': f,
            'version': d[items(d, f[0][1], f[0][2])[0][1]] + 1,
            'serial': d[f[1][1]:f[1][2]],
            'algorithm': NAMES[oid(d[items(d, f[2][1], f[2][2])[0][1]:items(d, f[2][1], f[2][2])[0][2]])],
            'issuer': name(d, f[3][1], f[3][2]), 'subject': name(d, f[5][1], f[5][2]),
            'from': when(d, *items(d, f[4][1], f[4][2])[0][1:3]),
            'until': when(d, *items(d, f[4][1], f[4][2])[1][1:3])}
    spki = items(d, f[6][1], f[6][2])
    alg = items(d, spki[0][1], spki[0][2])
    cert['curve'] = NAMES[oid(d[alg[1][1]:alg[1][2]])]
    point = d[spki[1][1] + 1:spki[1][2]]              # skip the unused-bits byte
    half = (len(point) - 1) // 2
    cert['Q'] = (int.from_bytes(point[1:1 + half], 'big'), int.from_bytes(point[1 + half:], 'big'))
    cert['extensions'] = []
    for _, xs, xe, _ in items(d, *tlv(d, f[7][1])[1:]):
        parts = items(d, xs, xe)
        key = NAMES.get(oid(d[parts[0][1]:parts[0][2]]), oid(d[parts[0][1]:parts[0][2]]))
        critical = len(parts) == 3 and d[parts[1][1]] != 0
        cert['extensions'].append((key, critical, extension(d, key, parts[-1][1], parts[-1][2])))
    rs = items(d, ss + 1, se)                          # BIT STRING holding SEQUENCE {r, s}
    cert['r'], cert['s'] = [int.from_bytes(d[a:b], 'big') for _, a, b, _ in items(d, rs[0][1], rs[0][2])]
    return cert

leaf, ca = read('munotes-leaf.pem'), read('ye1.pem')
print('THE CERTIFICATE munotes.in SERVED ON 30 SEPTEMBER 2026 (%d bytes of DER)' % len(leaf['der']))
print('  version               %d' % leaf['version'])
print('  serial number         %s (%d bytes)' % (leaf['serial'].hex().upper(), len(leaf['serial'])))
print('  signature algorithm   %s' % leaf['algorithm'])
print('  issuer                %s' % leaf['issuer'])
print('  not before            %s UTC' % leaf['from'])
print('  not after             %s UTC' % leaf['until'])
print('  subject               %s' % leaf['subject'])
print('  public key            EC point on %s' % leaf['curve'])
print('  extensions, %d of them:' % len(leaf['extensions']))
for key, critical, value in leaf['extensions']:
    print('    %-24s %-9s %s' % (key, 'critical' if critical else '', value))
span = leaf['until'] - leaf['from']
print('  validity              %s, so %d days counting both ends' % (span, span.days + 1))

print()
print('ITS ISSUER, %s' % ca['subject'])
print('  issued by             %s' % ca['issuer'])
print('  public key            EC point on %s' % ca['curve'])
for key, critical, value in ca['extensions']:
    if key in ('basicConstraints', 'keyUsage', 'subjectKeyIdentifier'):
        print('    %-24s %-9s %s' % (key, 'critical' if critical else '', value))
ski = [v for k, c, v in ca['extensions'] if k == 'subjectKeyIdentifier'][0]
aki = [v for k, c, v in leaf['extensions'] if k == 'authorityKeyIdentifier'][0]
print('  the leaf\'s authority key identifier equals this subject key identifier:', aki == ski)

# ---- ECDSA verification on P-384 (constants from FIPS 186-4 D.1.2.4) ---------------------
p = 2**384 - 2**128 - 2**96 + 2**32 - 1
n = 0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973
b = 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef
G = (0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7,
     0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f)
a = p - 3

def add(P, Q):
    if P is None: return Q
    if Q is None: return P
    if P[0] == Q[0] and (P[1] + Q[1]) % p == 0: return None
    if P == Q: lam = (3 * P[0] * P[0] + a) * pow(2 * P[1], -1, p) % p
    else:      lam = (Q[1] - P[1]) * pow(Q[0] - P[0], -1, p) % p
    x = (lam * lam - P[0] - Q[0]) % p
    return (x, (lam * (P[0] - x) - P[1]) % p)

def mul(k, P):
    R = None
    while k:
        if k & 1: R = add(R, P)
        P, k = add(P, P), k >> 1
    return R

def on_curve(P):
    return (P[1] ** 2 - (P[0] ** 3 + a * P[0] + b)) % p == 0

def verify(tbs, r, s, Q):
    e = int.from_bytes(hashlib.sha384(tbs).digest(), 'big')
    w = pow(s, -1, n)
    X = add(mul(e * w % n, G), mul(r * w % n, Q))
    return X is not None and X[0] % n == r

print()
print('THE CA\'S SIGNATURE, CHECKED WITH NOTHING BUT INTEGERS')
print('  P-384 base point is on the curve, and n times it is the point at infinity:',
      on_curve(G) and mul(n, G) is None)
print('  the issuer\'s public key is a point on P-384:', on_curve(ca['Q']))
tbs = leaf['der'][leaf['tbs'][0]:leaf['tbs'][1]]
print('  signed part of the leaf: %d bytes, hashed with SHA-384' % len(tbs))
print('  signature verifies with the issuer\'s key :', verify(tbs, leaf['r'], leaf['s'], ca['Q']))
forged = tbs.replace(b'munotes.in', b'munotes.io')
later = tbs.replace(b'261211115458Z', b'271211115458Z')
assert forged != tbs and later != tbs            # prove each edit really changed the bytes
print('  one name changed, munotes.in to munotes.io:', verify(forged, leaf['r'], leaf['s'], ca['Q']))
print('  expiry moved a year later                :', verify(later, leaf['r'], leaf['s'], ca['Q']))
munotes.in405

X.509: The Certificate and Its Fields

THE CERTIFICATE munotes.in SERVED ON 30 SEPTEMBER 2026 (918 bytes of DER)
  version               3
  serial number         05FFBEAB49E3BC6432532923E828FE118890 (18 bytes)
  signature algorithm   ecdsa-with-SHA384
  issuer                C=US, O=Let's Encrypt, CN=YE1
  not before            2026-09-12 11:54:59 UTC
  not after             2026-12-11 11:54:58 UTC
  subject               CN=munotes.in
  public key            EC point on P-256
  extensions, 10 of them:
    keyUsage                 critical  digitalSignature
    extKeyUsage                        serverAuth
    basicConstraints         critical  CA: no
    subjectKeyIdentifier               D84F8997681159F6...
    authorityKeyIdentifier             BB20CA470BFED7E5...
    authorityInfoAccess                http://ye1.i.lencr.org/
    subjectAltName                     munotes.in, www.munotes.in
    certificatePolicies                domain validated
    cRLDistributionPoints              http://ye1.c.lencr.org/17.crl
    CT timestamps                      2 signed timestamps from public logs
  validity              89 days, 23:59:59, so 90 days counting both ends

ITS ISSUER, C=US, O=Let's Encrypt, CN=YE1
  issued by             C=US, O=ISRG, CN=Root YE
  public key            EC point on P-384
    keyUsage                 critical  digitalSignature keyCertSign cRLSign
    basicConstraints         critical  CA: yes, path length 0
    subjectKeyIdentifier               BB20CA470BFED7E5...
  the leaf's authority key identifier equals this subject key identifier: True

THE CA'S SIGNATURE, CHECKED WITH NOTHING BUT INTEGERS
  P-384 base point is on the curve, and n times it is the point at infinity: True
  the issuer's public key is a point on P-384: True
  signed part of the leaf: 797 bytes, hashed with SHA-384
  signature verifies with the issuer's key : True
  one name changed, munotes.in to munotes.io: False
  expiry moved a year later                : False
munotes.in406

X.509: The Certificate and Its Fields

What the run establishes, in order.

munotes.in407

X.509: The Certificate and Its Fields

Every field is there, in RFC 5280's order, and each matches what openssl x509 -text prints for the same file: version 3, an 18-byte serial number, the issuer YE1, 90 days of validity, the subject munotes.in, a P-256 key, and ten extensions.

munotes.in408

X.509: The Certificate and Its Fields

The end-entity certificate and the CA certificate differ where the chapter says they do. munotes.in's key usage is digital signatures only and its basic constraints say CA: no; YE1's key usage adds certificate signing and revocation-list signing, and its basic constraints say CA: yes, path length 0.

The two certificates are linked by key identifiers. The leaf's authority key identifier equals YE1's subject key identifier.

The CA's signature verifies from nothing but integers. Hash the 797 signed bytes with SHA-384, run ECDSA verification on P-384 with YE1's public key, and the result is True.

Any change breaks it. One letter of the host name changed, munotes.in to munotes.io, and the signature fails. The expiry moved one year later, and it fails. This is the whole security of a certificate: its content can be read by anyone and changed by no one but the CA.

X.509's authentication procedures

Besides the certificate, X.509 describes how two parties who hold each other's certificates can authenticate. The current Recommendation keeps these in its Annex N, which describes five procedures; the three set in examinations are one-way, two-way and three-way. In each, a token is a small message signed by its sender.

The symbols: A{...} is information signed by A; tA is a timestamp holding an expiry; rA is a nonce, a number A never repeats; B is B's name; Bp[...] is information encrypted under B's public key.

One-way     A -> B    A{tA, rA, B}
Two-way     A -> B    A{tA, rA, B}
            B -> A    B{tB, rB, A, rA}
Three-way   A -> B    A{tA, rA, B}
            B -> A    B{tB, rB, A, rA}
            A -> B    A{rB, B}

Any token may also carry signed data (sgnData) and a secret for B encrypted under B's public key (Bp[encData]), which is how a session key is sent.

ProcedureWhat it provesWhat the receiver checksNeeds synchronised clocks
One-waythe identity of A; that the token was made by A and meant for B; its integrity and freshnessA's certificate has not expired; the signature; that B is the named recipient; the timestamp is current; optionally that rA is not a replayyes
Two-wayall of that, plus the same about B's reply to Athe same checks, made by A on B's tokenyes
Three-waythe same as two-wayeach side checks that its own nonce came backno: the timestamps may be zero
munotes.in409

X.509: The Certificate and Its Fields

The three-way procedure is what removes the clocks. In the two-way procedure freshness rests on timestamps, so both clocks must agree, which is the Kerberos trade again. In the three-way procedure each party sends a nonce and demands it back signed by the other, so freshness rests on the nonces, and the Recommendation says the timestamps "need not be checked".

The 2019 edition adds two five-way procedures involving a third party it calls a trust broker, which checks both certificates for A and B. They are recorded here so that a reference to "five-way authentication" is recognised; the one-, two- and three-way procedures are the ones to know.

Distinctions that carry marks

End-entity certificateCA certificate
Basic constraintsCA: no, or absentCA: yes, critical, with an optional path length
Key usagesignatures, key agreement, key enciphermentadds keyCertSign and cRLSign
Examplemunotes.inLet's Encrypt YE1
Critical extensionNon-critical extension
Software that does not recognise itmust reject the certificatemay ignore the extension
Software that recognises itmust process itmust process it
Used forrestrictions that must not be skipped: basic constraints, key usageinformation: key identifiers, where to fetch things
Version 1 (1988)Version 2 (1993)Version 3 (1996)
Unique identifiersnoyesyes
Extensionsnonoyes
In use todayrarelynoeverywhere

What beginners get wrong here

Saying a certificate is encrypted. It is signed, not encrypted. Anyone can read every field of it, as the run does. The signature stops it being changed, not read.

Saying the CA signs the whole certificate. It signs the tbsCertificate part. The signature cannot sign itself.

Saying the common name identifies the website. Software matches the host name against the subject alternative name, and RFC 9525 forbids using the common name for that.

Thinking a certificate says the owner is honest. It says a CA checked a name against a key, under a stated policy. A domain-validated certificate proves only that the applicant controlled the domain when it applied.

Thinking a certificate within its dates is safe to trust. It may have been revoked. Checking that is the next chapter.

Quick revision

  • X.509: ITU-T Recommendation, = ISO/IEC 9594-8; v1 1988, v2 1993 (unique identifiers), v3 June 1996 (extensions); Internet profile RFC 5280 (2008); current edition October 2019.
  • Three parts: tbsCertificate, signatureAlgorithm, signatureValue; the CA signs the hash of the first.
  • Fields: version, serial number, signature algorithm, issuer, validity (inclusive), subject, subject public key info, two unique identifiers, extensions.
  • Notation: CA<<A>> = CA {V, SN, AI, CA, UCA, A, UA, Ap, TA}; Y{I} is I signed by Y.
  • Critical extension not recognised means reject; non-critical may be ignored.
  • Key extensions: key usage, extended key usage, subject and authority key identifiers, certificate policies, subject alternative name (what browsers match), basic constraints (CA or not, path length), authority information access, CRL distribution points.
  • Authentication procedures: one-way A{tA, rA, B}; two-way adds B{tB, rB, A, rA}; three-way adds A{rB, B} and needs no synchronised clocks.
munotes.in410

X.509: The Certificate and Its Fields

Test yourself

1. Define an X.509 certificate and name its three top-level parts. It is a data structure, standardised by ITU-T X.509 and profiled for the Internet by RFC 5280, in which a certification authority binds a subject's name to a public key for a validity period and signs the result. Its three parts are the tbsCertificate, holding every field to be signed; the signatureAlgorithm, naming the algorithm the CA used; and the signatureValue, the CA's signature on the encoded tbsCertificate.

2. List the fields of a version 3 certificate and state what each is for. Version, which tells software which fields to expect; serial number, unique for the issuing CA; signature, the algorithm the CA used; issuer, the CA's name; validity, the not-before and not-after dates; subject, the owner's name; subject public key information, the algorithm and the key; the optional issuer and subject unique identifiers; and extensions, which carry everything else, such as key usage, basic constraints and the subject's alternative names.

3. Write the textbook's notation for a certificate of user A issued by a CA, and explain each symbol. CA<<A>> = CA {V, SN, AI, CA, UCA, A, UA, Ap, TA}. V is the version, SN the serial number, AI the signature algorithm identifier, CA the issuer's name, UCA the issuer's unique identifier, A the subject's name, UA the subject's unique identifier, Ap A's public key and TA the validity period; the braces preceded by CA mean the whole list is signed by the CA.

4. What does it mean for an extension to be marked critical? It means that software that does not recognise the extension, or cannot process what it contains, must reject the whole certificate. A non-critical extension may be ignored by software that does not recognise it. CAs mark critical the restrictions that must not be skipped, such as basic constraints and key usage.

munotes.in411

X.509: The Certificate and Its Fields

5. How do the basic constraints of munotes.in's certificate and of its issuer differ, and why? munotes.in's certificate says CA: no, so its key may never be accepted as the signer of another certificate; it belongs to a website, not an authority. Its issuer YE1 says CA: yes with a path length of zero, so it may sign certificates, but only end-entity ones: no further CA may appear below it in a chain.

6. Explain the three-way authentication procedure, and what it gains over the two-way one. A sends B a token signed by A containing a timestamp, a nonce rA and B's name; B replies with a token signed by B containing its own timestamp, a nonce rB, A's name and rA; A then sends a token signed by A containing rB and B's name. Each party checks that its own nonce came back signed by the other. Because freshness rests on the returned nonces, the timestamps need not be checked, so the parties do not need synchronised clocks, which the two-way procedure does.

7. A browser connects to www.munotes.in. Which field does it match the host name against? The subject alternative name extension, which lists munotes.in and www.munotes.in. The subject's common name is not used: RFC 9525 says the Common Name must not be used to identify a service.

Contents This chapter on its own page

munotes.in412

Chapter Sixty-Five

Certificate Chains, Revocation and the CRL

Syllabus topic Module 2, "Authentication Applications: X.509 Authentication"

In one line

A certificate is trusted by following signatures upward, one certificate at a time, until you reach a key you already trust; and it can be withdrawn before it expires, although in practice that withdrawal is the weakest part of the whole system.

In the words an answer should use: a certification path is an ordered list of certificates in which each is signed by the subject of the next, beginning with the end-entity certificate and ending at a trust anchor, a CA key the relying party trusts directly; the path is valid only if every signature verifies, every certificate is within its validity period and not revoked, the names chain correctly, and every issuer is authorised to act as a CA. Revocation is the CA's declaration, before expiry, that a certificate must no longer be trusted, published in a signed certificate revocation list (CRL) or given on request by an OCSP responder.

Why there is a chain at all

A browser ships with a short list of certificates it trusts outright, its trust store. If every website's certificate were signed directly by one of those roots, each root's private key would be in use every minute of every day, and one mistake with it would compromise every site at once.

So a root's key is used rarely and kept offline. It signs a few intermediate CA certificates, and those intermediates sign the certificates of websites. The chain is how trust reaches a website from a key that almost never comes out of its vault. If an intermediate is compromised, it can be revoked and replaced without replacing the root in every browser in the world.

The vocabulary, from the Recommendation

X.509's 2019 edition defines the terms, and they are the ones to use.

TermMeaning
Certification pathan ordered list of certificates, starting with one signed by the trust anchor and ending with the end-entity certificate being validated; each intermediate certificate is a CA certificate whose subject is the issuer of the next
Trust anchoran entity the relying party trusts and uses to validate certificates, normally a root CA's key in the trust store
End entitythe certificate's final subject, a website or a person, which is not a CA
Self-issued certificatea CA certificate whose issuer and subject are the same CA
Self-signed certificatea self-issued certificate signed with the private key matching the public key inside it; roots are distributed this way
Cross-certificatea CA certificate whose issuer and subject are different CAs; one CA vouches for another

A self-signed root proves nothing by its signature. Anyone can make a certificate that signs itself. A root is trusted because it is in the store, placed there by the browser or the operating system, and its signature shows only that the copy has not been altered.

munotes.in413

Certificate Chains, Revocation and the CRL

The textbook's picture: forward and reverse certificates

The prescribed textbook describes CAs arranged in a hierarchy in which each CA's directory entry holds two kinds of certificate:

  • Forward certificates: certificates of the CA, issued by other CAs.
  • Reverse certificates: certificates issued by the CA, certifying other CAs.

With both kinds available, a user can build a path to any other user by walking up the hierarchy to a common ancestor and down again. A worked example, in the notation of the previous chapter, where Y<<X>> is the certificate of X issued by Y.

The setting. Asha is certified by a CA called MUMBAI; Bharat by a CA called PUNE; both CAs are certified by a national CA called INDIA. Asha holds MUMBAI's public key and trusts it. She needs Bharat's public key.

The path she builds:

MUMBAI<<INDIA>>  INDIA<<PUNE>>  PUNE<<Bharat>>
  1. MUMBAI<<INDIA>> is a reverse certificate of MUMBAI's: MUMBAI certifies its parent. Asha verifies it with MUMBAI's key, which she trusts, and so learns INDIA's key.
  2. INDIA<<PUNE>> is a forward certificate of PUNE's: PUNE certified by its parent. Asha verifies it with INDIA's key, just learned, and so learns PUNE's key.
  3. PUNE<<Bharat>> is Bharat's own certificate. Asha verifies it with PUNE's key, and so has Bharat's key, vouched for through a chain of three signatures.

Bharat, going the other way, builds PUNE<<INDIA>> INDIA<<MUMBAI>> MUMBAI<<Asha>>. The hierarchy is walked in whichever direction the user needs, which is why each CA holds certificates in both directions. The current Recommendation calls every CA certificate between different CAs a cross-certificate; forward and reverse are the textbook's names for its two directions in a hierarchy.

The real thing: munotes.in's chain

munotes.in's server sent four certificates on 30 September 2026. Its own; its issuer's, Let's Encrypt YE1; and two more that exist because Let's Encrypt is in the middle of changing roots.

Root YE is a new root, whose own certificate dates from 3 September 2025, and not every device yet has it in its store. So the older root ISRG Root X2 has signed a certificate for Root YE, and the still older ISRG Root X1, which almost every device trusts, has signed one for X2. Those two are cross-certificates, exactly the Recommendation's term. A device that trusts only X1 can follow the chain all the way up; a device that already trusts Root YE can stop two links earlier.

The two files below are what the server sent, and the root certificate an older device has in its store.

-----BEGIN CERTIFICATE-----
MIIDkjCCAxmgAwIBAgISBf++q0njvGQyUykj6Cj+EYiQMAoGCCqGSM49BAMDMDMx
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNZ
RTEwHhcNMjYwOTEyMTE1NDU5WhcNMjYxMjExMTE1NDU4WjAVMRMwEQYDVQQDEwpt
dW5vdGVzLmluMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtlAU0VbQfEk4HTrX
5hIWuUpiQ20GHhNVuqE+ZDVD9ULSrOf4Kg8qSn0gtLQPRk5fVBf/1lUus8ioEKMA
V2+Tt6OCAikwggIlMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcD
ATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTYT4mXaBFZ9hSOXEPu3Mg0+5NmNTAf
BgNVHSMEGDAWgBS7IMpHC/7X5Zz5jwkqo4w3RbG82DAzBggrBgEFBQcBAQQnMCUw
IwYIKwYBBQUHMAKGF2h0dHA6Ly95ZTEuaS5sZW5jci5vcmcvMCUGA1UdEQQeMByC
Cm11bm90ZXMuaW6CDnd3dy5tdW5vdGVzLmluMBMGA1UdIAQMMAowCAYGZ4EMAQIB
MC4GA1UdHwQnMCUwI6AhoB+GHWh0dHA6Ly95ZTEuYy5sZW5jci5vcmcvMTcuY3Js
MIIBDQYKKwYBBAHWeQIEAgSB/gSB+wD5AHYA1219ENGn9XfCx+lf1wC/+YLJM1pl
4dCzAXMXwMjFaXcAAAGgla4X3QAABAMARzBFAiBCYhMg08b9CrBS19uyuz3wZStw
W7DE7t8ZSDPcUvBD4gIhAMD7yhJN64KP6bWG5H6ZA68VLvNZCiRLQrsvnH92e8bO
AH8AqCbL4wrGNRJGUz/gZfFPGdluGQgTxB3ZbXkAsxI8VScAAAGgla4azQAIAAAF
ADH5KHsEAwBIMEYCIQCQybl79cSWejo8WdCZ3O4BS7uIG/gVa+gwzM4PHheFCgIh
AIgzHibZCL4C2UUgOwU0rHkIHnhr05PVx6KajQca6AZiMAoGCCqGSM49BAMDA2cA
MGQCMAR9Skrz1wHkFWwr/d/D28Pjlo8+ZPALlN7CuZG728/ZJy+h/Wn68R5HhziI
YSN6OwIwRID/awRkE2KqxXBIPSQ+zj2S4d/UivmhZt2GHve0KRjTiuh1JUdx4iH1
MqrwigZp
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICizCCAhGgAwIBAgIQXd1w3TH4AchcGGp6BLgK/jAKBggqhkjOPQQDAzAuMQsw
CQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZRTAeFw0y
NTA5MDMwMDAwMDBaFw0yODA5MDIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYwFAYD
VQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNZRTEwdjAQBgcqhkjOPQIBBgUr
gQQAIgNiAAQHZVB1/mimla2hfSurylScjPMZaOJXLz/NnAc2sylm8WDyhU9Ccp+z
ASQi5vSwGGJjSGklkD9fdPR8GpyDIOIjCEfrnbt/v+ZSEPLLEGbaM6EccDbN7p9x
teIm2Avf+ryjge4wgeswDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUF
BwMBMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLsgykcL/tflnPmPCSqj
jDdFsbzYMB8GA1UdIwQYMBaAFKPIJlqOoUzQNWP8myPIOq5W809WMDIGCCsGAQUF
BwEBBCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3llLmkubGVuY3Iub3JnLzATBgNV
HSAEDDAKMAgGBmeBDAECATAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8veWUuYy5s
ZW5jci5vcmcvMAoGCCqGSM49BAMDA2gAMGUCMQDgjUEahFT/h3DRakqiPZpLvPgf
Zwkt6K2EOMmh1nvEzl83eMLYcod4GCl3b0J1Nn0CMBNYmEQJb4CEG5WoOe7aRn/L
VKu6saHmHEynI7ysIPd8zQsK1HdmhlHKlw9Z5GpGvA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICpjCCAiugAwIBAgIRAIchZfw0tuX7qK3Vs3BftTowCgYIKoZIzj0EAwMwTzEL
MAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNo
IEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDIwHhcNMjYwNTEzMDAwMDAwWhcN
MzIwOTAyMjM1OTU5WjAuMQswCQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4G
A1UEAxMHUm9vdCBZRTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDwS/6vhrcVqcbBo
+wgdI3fwn9x7DNJJOY/lTOti0vkwuRN87RhEhTH17E7XyFjWsPYhIPt/wzOqxTd2
b+4ZJNy9ID04YywF9U5zasDVyGSNErVNtz8uSGh5izW87j77GaOB6zCB6DAOBgNV
HQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUo8gmWo6hTNA1Y/ybI8g6rlbzT1YwHwYDVR0jBBgwFoAUfEKW
rt5LSDv6kviejM9ti6lyN5UwMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAChhZo
dHRwOi8veDIuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcGA1Ud
HwQgMB4wHKAaoBiGFmh0dHA6Ly94Mi5jLmxlbmNyLm9yZy8wCgYIKoZIzj0EAwMD
aQAwZgIxAMU19WCtmxVND8UHBZRoma49Z7jPs64Dma0eTu1OChVbB/2J7GV3nvYK
Ax54uk1G9QIxAO0miLVJu8PLNiXXXkiE/gsK3CTRTF/aeo4bMX42Zw40csRU6AC2
6hSW1/IWaas6dg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEcDCCAligAwIBAgIQbI8dxyfHEX97r4U6yYD5zTANBgkqhkiG9w0BAQsFADBP
MQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFy
Y2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTAeFw0yNjA1MTMwMDAwMDBa
Fw0zMjA5MDIyMzU5NTlaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5l
dCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgy
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H
ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7
AlF9ItgKbppbd9/w+kHsOdx1ymgHDB/qo4H1MIHyMA4GA1UdDwEB/wQEAwIBBjAd
BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAd
BgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwHwYDVR0jBBgwFoAUebRZ5nu2
5eQBc4AIiMgaWPbpm24wMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAChhZodHRw
Oi8veDEuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcGA1UdHwQg
MB4wHKAaoBiGFmh0dHA6Ly94MS5jLmxlbmNyLm9yZy8wDQYJKoZIhvcNAQELBQAD
ggIBAD2/e9frmMxNpCV03qUHegg+MV2wz9644YoXdqtH8RyWYcBO7xfjjGEXdU1e
/o0OkEFiynUCOSIk/vLLo7ttz6CPAeNlWfC0XNkoGeWgK6jjXvozBaGuGH5n0Ufo
shMeWTuURqNN5G00sSXDTBrpp2+mgvdZQjb8K11TYMA25QA+YHNfbIEL0BniAhKS
2gsnJjSzrdZLI+EZ7SEyqdR2rkjd1KutLDU+n3TFyxjniZVGur4YlhMP3mY/dV95
IruAkkjOZier6hGBdEgZXXvaCz9u9iVEadsIE75pAGL8oHV5vxdARDiotRpul1IN
/UZwzAbrfUFcw1HkAcYD/mlZfnQ2ieCF2MS7j3Vhv7JPDKp45fmykmzYNSrumRW0
upFFKDBOoF7hsOb7oLyHS+Uft6jOUfOrogj8YUx38hKb2K20r42OgsSdDdxdeYWc
MS3Sb6mwJeSZEYxJ2gaXnDSPaKhhrNkYwljyVQyr4Nq+MEJytXNTnHqaAcrNwZlV
pcJL1KBnMrMjP7eanvUwL3FYj3cF17jtboLt7gLoi4+2rWZFvn+w54jmd/FIuhhZ
cEaU/wvU6BUNMtcVquVGHp7itQeDth5j+XL3j4WJ2SABwzUl6OeYdgpIt/ITZa+p
TT0mQ/r5XyA4MEAiabn7XJjvCERlF2dcn2wqJw+CreTkkQ2R
-----END CERTIFICATE-----
munotes.in414

Certificate Chains, Revocation and the CRL

-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----

And the same Root YE key in the certificate it signs for itself, which newer stores are receiving:

-----BEGIN CERTIFICATE-----
MIIB2TCCAWCgAwIBAgIRAKQCa6LvbHwg1AR+XmWmk4AwCgYIKoZIzj0EAwMwLjEL
MAkGA1UEBhMCVVMxDTALBgNVBAoTBElTUkcxEDAOBgNVBAMTB1Jvb3QgWUUwHhcN
MjUwOTAzMDAwMDAwWhcNNDUwOTAyMjM1OTU5WjAuMQswCQYDVQQGEwJVUzENMAsG
A1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZRTB2MBAGByqGSM49AgEGBSuBBAAi
A2IABDwS/6vhrcVqcbBo+wgdI3fwn9x7DNJJOY/lTOti0vkwuRN87RhEhTH17E7X
yFjWsPYhIPt/wzOqxTd2b+4ZJNy9ID04YywF9U5zasDVyGSNErVNtz8uSGh5izW8
7j77GaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFKPIJlqOoUzQNWP8myPIOq5W809WMAoGCCqGSM49BAMDA2cAMGQCMHhMr8N9
LdL1VQKs9BdV81r76eXRB6mtjuNjzk6/lBsPNToWLTDzGYgtQKO1jl63uAIwGV7m
onyF377c+MM1oqVNs17sgu7F9YKZwgLmVbeOMDbKAXHtKMDLbiGllCcs8f47
-----END CERTIFICATE-----

The run: validating the path

The listing follows RFC 5280 s.6.1: starting from munotes.in's certificate, find the certificate whose subject is its issuer; check that the issuer is a CA, that its key verifies the signature, and that every certificate so far is within its dates; and repeat until a certificate is signed by a trust anchor. Signatures are verified from integers alone: ECDSA on P-384, whose constants come from FIPS 186-4, and RSA with PKCS #1 version 1.5 padding, from RFC 8017.

# Build and check munotes.in's certification path, link by link, as RFC 5280 s.6.1 does.
import base64, datetime, hashlib

def pems(path):                           # every certificate in a PEM file, as DER bytes
    out, cur = [], None
    for line in open(path).read().split('\n'):
        if line.startswith('-----BEGIN'): cur = []
        elif line.startswith('-----END'): out.append(base64.b64decode(''.join(cur))); cur = None
        elif cur is not None: cur.append(line)
    return out

def tlv(d, i):
    tag, n, j = d[i], d[i + 1], i + 2
    if n & 0x80:
        k = n & 0x7F
        n, j = int.from_bytes(d[j:j + k], 'big'), j + k
    return tag, j, j + n

def items(d, s, e):
    out = []
    while s < e:
        t, a, b = tlv(d, s)
        out.append((t, a, b, s)); s = b
    return out

def oid(b):
    arcs, v = [b[0] // 40, b[0] % 40], 0
    for x in b[1:]:
        v = (v << 7) | (x & 0x7F)
        if not x & 0x80: arcs.append(v); v = 0
    return '.'.join(map(str, arcs))

def name(d, s, e):
    return ', '.join('%s=%s' % ({'2.5.4.3': 'CN', '2.5.4.6': 'C', '2.5.4.10': 'O'}[oid(d[a:b])],
                                d[c:f].decode())
                     for _, s1, e1, _ in items(d, s, e) for _, s2, e2, _ in items(d, s1, e1)
                     for (_, a, b, _), (_, c, f, _) in [items(d, s2, e2)])

def parse(d):
    _, s, e = tlv(d, 0)
    (_, ts, te, t0), _, (_, ss, se, _) = items(d, s, e)
    f = items(d, ts, te)
    alg = oid(d[items(d, f[2][1], f[2][2])[0][1]:items(d, f[2][1], f[2][2])[0][2]])
    v = items(d, f[4][1], f[4][2])
    c = {'tbs': d[t0:te], 'issuer': name(d, f[3][1], f[3][2]), 'subject': name(d, f[5][1], f[5][2]),
         'from': datetime.datetime.strptime(d[v[0][1]:v[0][2]].decode(), '%y%m%d%H%M%SZ'),
         'until': datetime.datetime.strptime(d[v[1][1]:v[1][2]].decode(), '%y%m%d%H%M%SZ'),
         'hash': hashlib.sha384 if alg.endswith('4.3.3') else hashlib.sha256,
         'rsa_signed': alg == '1.2.840.113549.1.1.11', 'ca': False, 'path': None}
    spki = items(d, f[6][1], f[6][2])
    key = d[spki[1][1] + 1:spki[1][2]]                     # skip the unused-bits byte
    if oid(d[items(d, spki[0][1], spki[0][2])[0][1]:items(d, spki[0][1], spki[0][2])[0][2]]) \
            == '1.2.840.113549.1.1.1':                     # RSA: SEQUENCE {n, e}
        (_, a, b, _), (_, x, y, _) = items(key, *tlv(key, 0)[1:])
        c['key'] = ('rsa', int.from_bytes(key[a:b], 'big'), int.from_bytes(key[x:y], 'big'))
    else:                                                  # EC: 04 || X || Y
        h = (len(key) - 1) // 2
        c['key'] = ('ec', int.from_bytes(key[1:1 + h], 'big'), int.from_bytes(key[1 + h:], 'big'))
    for _, xs, xe, _ in items(d, *tlv(d, f[7][1])[1:]):
        p = items(d, xs, xe)
        if oid(d[p[0][1]:p[0][2]]) == '2.5.29.19':          # basic constraints
            inner = items(d, *tlv(d, p[-1][1])[1:])
            c['ca'] = bool(inner and inner[0][0] == 1 and d[inner[0][1]])
            c['path'] = next((int.from_bytes(d[a:b], 'big')
                              for t, a, b, _ in inner if t == 2), None)
    sig = d[ss + 1:se]
    if c['rsa_signed']:
        c['sig'] = int.from_bytes(sig, 'big')
    else:
        c['sig'] = tuple(int.from_bytes(sig[a:b], 'big')
                         for _, a, b, _ in items(sig, *tlv(sig, 0)[1:]))
    return c

# ---- P-384 (FIPS 186-4 D.1.2.4) and RSA PKCS #1 v1.5 (RFC 8017) verification ---------------
p = 2**384 - 2**128 - 2**96 + 2**32 - 1
n = 0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973
G = (0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7,
     0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f)

def add(P, Q):
    if P is None: return Q
    if Q is None: return P
    if P[0] == Q[0] and (P[1] + Q[1]) % p == 0: return None
    lam = ((3 * P[0] * P[0] - 3) * pow(2 * P[1], -1, p) if P == Q
           else (Q[1] - P[1]) * pow(Q[0] - P[0], -1, p)) % p
    x = (lam * lam - P[0] - Q[0]) % p
    return (x, (lam * (P[0] - x) - P[1]) % p)

def mul(k, P):
    R = None
    while k:
        if k & 1: R = add(R, P)
        P, k = add(P, P), k >> 1
    return R

SHA256_PREFIX = bytes.fromhex('3031300d060960864801650304020105000420')

def signature_ok(cert, issuer_key):
    digest = cert['hash'](cert['tbs']).digest()
    if issuer_key[0] == 'rsa':
        _, modulus, e = issuer_key
        size = (modulus.bit_length() + 7) // 8
        em = pow(cert['sig'], e, modulus).to_bytes(size, 'big')
        want = b'\x00\x01' + b'\xff' * (size - 3 - 51) + b'\x00' + SHA256_PREFIX + digest
        return em == want
    r, s = cert['sig']
    w = pow(s, -1, n)
    X = add(mul(int.from_bytes(digest, 'big') * w % n, G), mul(r * w % n, issuer_key[1:]))
    return X is not None and X[0] % n == r

def cn(dn):
    return dn.split('CN=')[1]

def validate(chain, anchors, at):
    """RFC 5280 s.6.1, the checks that decide this path: signature, dates, names, CA flag."""
    path, cert = [chain[0]], chain[0]
    while True:
        anchor = next((a for a in anchors if a['subject'] == cert['issuer']
                       and signature_ok(cert, a['key'])), None)
        if anchor:
            return path + [anchor], 'valid'
        issuer = next((c for c in chain if c['subject'] == cert['issuer']
                       and c is not cert), None)
        if issuer is None:
            return path, 'refused: issuer %s is neither sent nor trusted' % cn(cert['issuer'])
        if not issuer['ca']:
            return path, 'refused: %s is not a CA' % cn(issuer['subject'])
        if not signature_ok(cert, issuer['key']):
            return path, 'refused: signature on %s does not verify' % cn(cert['subject'])
        for c in path + [issuer]:
            if not c['from'] <= at <= c['until']:
                return path, 'refused: %s is outside its validity period' % cn(c['subject'])
        path.append(issuer)
        cert = issuer

served = [parse(d) for d in pems('served-chain.pem')]
x1 = parse(pems('trusted-root-x1.pem')[0])
ye_self = parse(pems('root-ye-self-signed.pem')[0])
now = datetime.datetime(2026, 9, 30, 12, 0, 0)

print('WHAT THE SERVER SENT: %d certificates' % len(served))
for i, c in enumerate(served):
    print('  %d. subject %-14s issued by %s' % (i, cn(c['subject']), cn(c['issuer'])))
print()
print('THE TRUST ANCHOR in this client\'s store:', cn(x1['subject']))
print('  its own signature verifies with its own key:', signature_ok(x1, x1['key']),
      '(proves integrity only: it is trusted because it is in the store)')

print()
print('EACH LINK, CHECKED ON 30 SEPTEMBER 2026')
for cert, issuer in zip(served, served[1:] + [x1]):
    kind = 'RSA-%d' % issuer['key'][1].bit_length() if issuer['key'][0] == 'rsa' else 'ECDSA P-384'
    print('  %-12s signed by %-12s %-12s signature %-5s CA flag on issuer %s' % (
        cn(cert['subject']), cn(issuer['subject']), kind,
        signature_ok(cert, issuer['key']), issuer['ca']))

path, verdict = validate(served, [x1], now)
print()
print('PATH FROM munotes.in TO A TRUSTED ROOT:', ' -> '.join(cn(c['subject']) for c in path))
print('  verdict:', verdict)
print('  path length limits: YE1 allows %d CA below it, and none is used' % served[1]['path'])

print()
print('WHAT A VALIDATOR MUST REFUSE')
print('  the intermediate YE1 left out         :', validate([served[0]] + served[2:], [x1], now)[1])
print('  checked on 1 January 2027             :',
      validate(served, [x1], datetime.datetime(2027, 1, 1))[1])
tampered = dict(served[1], tbs=served[1]['tbs'].replace(b'YE1', b'YE9'))
assert tampered['tbs'] != served[1]['tbs']
print('  YE1 altered by one character          :',
      validate([served[0], tampered] + served[2:], [x1], now)[1])
print('  a client whose store is empty         :', validate(served, [], now)[1])

print()
print('ONE KEY, TWO CERTIFICATES: Root YE, self-signed and cross-signed')
print('  same public key in both               :', ye_self['key'] == served[2]['key'])
for label, c in (('self-signed ', ye_self), ('cross-signed', served[2])):
    print('  %s                          : issued by %-14s valid %s to %s'
          % (label, cn(c['issuer']), c['from'].date(), c['until'].date()))
short, verdict = validate(served, [ye_self], now)
print('  a newer store that trusts Root YE     :',
      ' -> '.join(cn(c['subject']) for c in short), verdict)
munotes.in415

Certificate Chains, Revocation and the CRL

WHAT THE SERVER SENT: 4 certificates
  0. subject munotes.in     issued by YE1
  1. subject YE1            issued by Root YE
  2. subject Root YE        issued by ISRG Root X2
  3. subject ISRG Root X2   issued by ISRG Root X1

THE TRUST ANCHOR in this client's store: ISRG Root X1
  its own signature verifies with its own key: True (proves integrity only: it is trusted because it is in the store)

EACH LINK, CHECKED ON 30 SEPTEMBER 2026
  munotes.in   signed by YE1          ECDSA P-384  signature True  CA flag on issuer True
  YE1          signed by Root YE      ECDSA P-384  signature True  CA flag on issuer True
  Root YE      signed by ISRG Root X2 ECDSA P-384  signature True  CA flag on issuer True
  ISRG Root X2 signed by ISRG Root X1 RSA-4096     signature True  CA flag on issuer True

PATH FROM munotes.in TO A TRUSTED ROOT: munotes.in -> YE1 -> Root YE -> ISRG Root X2 -> ISRG Root X1
  verdict: valid
  path length limits: YE1 allows 0 CA below it, and none is used

WHAT A VALIDATOR MUST REFUSE
  the intermediate YE1 left out         : refused: issuer YE1 is neither sent nor trusted
  checked on 1 January 2027             : refused: munotes.in is outside its validity period
  YE1 altered by one character          : refused: signature on YE1 does not verify
  a client whose store is empty         : refused: issuer ISRG Root X1 is neither sent nor trusted

ONE KEY, TWO CERTIFICATES: Root YE, self-signed and cross-signed
  same public key in both               : True
  self-signed                           : issued by Root YE        valid 2025-09-03 to 2045-09-02
  cross-signed                          : issued by ISRG Root X2   valid 2026-05-13 to 2032-09-02
  a newer store that trusts Root YE     : munotes.in -> YE1 -> Root YE valid
munotes.in416

Certificate Chains, Revocation and the CRL

What the run establishes, in order.

munotes.in417

Certificate Chains, Revocation and the CRL

The server sends the chain in order, each certificate's issuer the next one's subject, and does not send the root: the root must come from the client's own store, or it would prove nothing.

The root's own signature verifies, and that is beside the point. It proves the copy is intact. The trust comes from its being in the store.

Every link verifies, with two different algorithms. Three links are ECDSA on P-384; the last, X2 signed by X1, is RSA with a 4,096-bit key, because X1 is an RSA root. Every issuer carries the CA flag. The path is valid from munotes.in to ISRG Root X1, five certificates long.

Four things a validator must refuse, it refuses. With YE1 left out, the path cannot be built. On 1 January 2027 munotes.in's certificate has expired. With one character of YE1 altered, YE1's signature no longer verifies. And a client with an empty store finds nothing to trust, however valid every signature is.

One key, two certificates, two paths. The self-signed Root YE and the cross-signed Root YE carry the same public key. A newer store that trusts Root YE directly validates munotes.in in three links, and an older one that trusts only X1 needs five. This is cross-certification doing its job: bringing a new root into use before every device knows it.

munotes.in418

Certificate Chains, Revocation and the CRL

What a validator checks

RFC 5280 s.6.1.3 lists what must hold for every certificate in the path, and s.6.1.4 adds what must hold for every certificate that acts as an issuer. An answer on path validation lists them.

For every certificate:

  1. Its signature verifies with the public key of the certificate above it (for the top one, the trust anchor's key).
  2. Its validity period includes the current time.
  3. At the current time it is not revoked.
  4. Its issuer name equals the subject name of the certificate above it.

For every certificate used as an issuer:

  1. It is version 3 and its basic constraints say it is a CA.
  2. Its path length limit, if present, is not exceeded by the CA certificates below it.
  3. Its key usage, if present, includes signing certificates.

And throughout, any name constraints and certificate policies are applied. Check 3 is the one the rest of this chapter is about, because it is the one that fails in practice.

Revocation: why it exists

A certificate states a binding for a period. Things can go wrong before the period ends, and RFC 5280 lists the reasons a CA may give:

CodeReasonExample
1keyCompromisethe website's private key was stolen
2cACompromisethe issuing CA's own key was stolen
3affiliationChangedthe subject's name or organisation has changed
4supersededthe certificate has been replaced by a new one
5cessationOfOperationthe site or service no longer exists
6certificateHoldsuspended, perhaps temporarily
9privilegeWithdrawna privilege the certificate asserted has been withdrawn

Code 0, unspecified, exists, and RFC 5280 says a CA should simply leave the reason out rather than use it. Code 8, removeFromCRL, is used only in delta lists, described below.

Revocation is the only answer to a stolen key. Without it, a thief who copies a website's private key could impersonate the site until the certificate expired.

The certificate revocation list

A CRL is a list, signed by the CA, of the serial numbers of certificates it has revoked and that have not yet expired. RFC 5280 s.5.1 defines its fields:

FieldWhat it holds
versionversion 2 when extensions are used
signaturethe algorithm the CA used to sign the list
issuerthe CA that issued the list
thisUpdatewhen this list was issued
nextUpdatethe latest time by which the next list will be issued
revokedCertificatesfor each: the serial number, the revocation date, and optionally a reason code
crlExtensionsfor example the CRL number, which increases with each list, and the authority key identifier
munotes.in419

Certificate Chains, Revocation and the CRL

The CA signs the whole list, so a client can check it came from the CA and has not been altered, exactly as it checks a certificate. A delta CRL lists only the changes since a full base list, so that clients need not download everything each time.

The real list, measured. munotes.in's certificate names a CRL at http://ye1.c.lencr.org/17.crl. Fetched on 30 September 2026 and read with OpenSSL, it was 62,440 bytes long, issued at 09:31:55 UTC that day with the next list promised by 9 October, and it named 1,599 revoked certificates. Only 210 entries gave a reason: 107 key compromise, 86 cessation of operation, 17 superseded; the other 1,389 gave none, as RFC 5280 prefers to unspecified. munotes.in's serial number was not on it, and its signature verified with YE1's key.

That one list is a single shard. The certificate names list number 17 of YE1's lists: large CAs now split their revocations across many lists so that no single list grows without limit, which is itself the first sign of the CRL's problem.

The costs of a CRL

  • Size. Every client that checks must download the list, and the list grows with every revocation. One shard of one intermediate of one CA held 1,599 entries.
  • Freshness. A list is only as current as its thisUpdate. A certificate revoked an hour after a list was issued is not on it until the next one.
  • Timing. A client that must fetch a list before it trusts a website has made the website slower to open.

OCSP: asking about one certificate

The Online Certificate Status Protocol, RFC 6960 (June 2013), replaces the download with a question. The client sends the certificate's identity to the CA's OCSP responder and receives a signed answer with one of three values:

  • good: no certificate with that serial number, within its validity period, is revoked;
  • revoked: it has been revoked, permanently or on hold;
  • unknown: the responder does not know about that certificate, usually because it does not serve that issuer.

It is smaller and fresher than a list, and it brought two new problems. Privacy: every OCSP query tells the CA which website the user is about to visit, from which address. Availability: if the responder does not answer, the client must choose between refusing the site and ignoring the check. OCSP stapling lets the website fetch the signed answer itself and attach it to its own handshake, which removes both problems for that site, at the cost of the website having to do it.

munotes.in420

Certificate Chains, Revocation and the CRL

The honest account: revocation mostly does not work

Every textbook explains CRLs and OCSP as though browsers used them. The browser makers' and the CAs' own documents say otherwise.

Checks were soft-fail. A client that cannot reach the responder usually proceeds anyway, because refusing would break websites whenever a CA's server is slow. But an attacker who has stolen a key and can intercept a victim's connection can also block the victim's revocation check, so a soft-fail check fails exactly when it is needed.

Chrome does not generally check online. Chromium's own page on revocation states that "Online (i.e. OCSP and CRL) checks are not generally performed by Chrome". Instead Chrome receives CRLSets, a compact list Google builds and pushes to browsers, which Chromium describes as "the primary means by which Chrome quickly blocks certificates in emergency situations".

Firefox built a different answer. Mozilla enabled CRLite for all desktop users from Firefox 137, in 2025: the browser downloads a compressed summary of every revocation in the public system, on average 300 kB a day, and checks locally, telling no one which sites it visits. Mozilla's post on it, 19 August 2025, records that OCSP requests "block the TLS handshake for 100 ms at the median", and that Firefox 142 would stop using OCSP for domain-validated certificates.

Let's Encrypt has turned OCSP off. Let's Encrypt, which issued munotes.in's certificate, announced on 5 December 2024 that it would end OCSP, citing the privacy risk. It removed OCSP addresses from its certificates on 7 May 2025 and turned its responders off on 6 August 2025; it now publishes revocations "exclusively via Certificate Revocation Lists". That is why munotes.in's certificate names a CRL and no OCSP responder.

The rules have followed. The CA/Browser Forum's Baseline Requirements, the rules a CA must follow for browsers to trust its website certificates, version 2.3.0 of 7 September 2026, require CRLs (republished at least every four days by a CA that offers no OCSP, every seven by one that does, and within 24 hours of any revocation) and leave OCSP optional.

And the real answer is to need revocation less. If a certificate expires soon after a key is stolen, the damage window is short whether or not anyone checks. The same Requirements now cap a website certificate's life on a fixed schedule:

Certificates issuedMaximum validity
before 15 March 2026398 days
from 15 March 2026200 days
from 15 March 2027100 days
from 15 March 202947 days

The Forum adopted that schedule as Ballot SC-081v3 on 11 April 2025, with 25 certificate issuers in favour, none against and five abstaining, and all four browser makers, Apple, Google, Microsoft and Mozilla, in favour. The Requirements go one step further: a short-lived certificate, valid for seven days or less, need not be revocable at all.

munotes.in421

Certificate Chains, Revocation and the CRL

Distinctions that carry marks

CRLOCSP
What the client getsthe whole list of revoked serialsan answer about one certificate
Sizegrows with revocationssmall, fixed
Freshnessas of thisUpdateas of the response
Privacythe CA learns nothing about browsingthe CA learns which site is visited, unless stapled
Signed bythe CAthe CA or a responder it authorises
Status in 2026required by the Baseline Requirementsoptional; Let's Encrypt has ended it
ExpiryRevocation
Decidedwhen the certificate is issuedafterwards, when something goes wrong
Where it is writtenin the certificate, notAfteron a CRL or in an OCSP answer
Can a client miss itno: the date is in the certificateyes, if it does not check or cannot reach the source
Forward certificateReverse certificate
In a hierarchy, for a CA Xa certificate of X issued by another CAa certificate issued by X for another CA
In the current Recommendationboth are cross-certificatesboth are cross-certificates

What beginners get wrong here

Trusting a root because its signature verifies. A self-signed signature proves only that the copy is intact. Trust comes from the root's place in the store.

Thinking the server sends the root. It sends the chain below the root. A root it sent could not be trusted anyway, for the reason above.

Saying an expired certificate is revoked, or the reverse. Expiry is written into the certificate at issue; revocation is declared later, on a list or by a responder.

Believing browsers check a CRL or OCSP on every connection. Chrome does not generally check online at all, Firefox checks a local summary, and Let's Encrypt no longer runs OCSP.

Writing that OCSP replaced the CRL. In 2026 it is the other way round: the CAs' rules require CRLs and make OCSP optional.

Quick revision

  • Certification path: from the end-entity certificate up, each signed by the next, ending at a trust anchor in the store. A self-signed root's signature proves integrity only.
  • Why chains: the root's key stays offline; intermediates sign; an intermediate can be revoked alone.
  • Textbook: forward certificates (of X, by other CAs) and reverse certificates (by X, for other CAs); path notation MUMBAI<<INDIA>> INDIA<<PUNE>> PUNE<<Bharat>>. Current term: cross-certificate.
  • RFC 5280 s.6.1.3: for every certificate, signature, validity, not revoked, issuer name matches; for every issuer, CA flag, path length, key usage.
  • CRL: signed list of revoked serials with dates and reasons; thisUpdate, nextUpdate; delta CRLs. Reasons 1 to 6 and 9; leave out rather than unspecified.
  • OCSP (RFC 6960): good, revoked or unknown; privacy leak; stapling.
  • 2026: Chrome does not generally check online (CRLSets); Firefox CRLite from 137; Let's Encrypt OCSP off 6 August 2025; Baseline Requirements: CRLs required, OCSP optional; maximum life 200, 100, then 47 days by 2029; certificates of 7 days or less need not be revocable.
munotes.in422

Certificate Chains, Revocation and the CRL

Test yourself

1. What is a certification path, and where must it end? It is an ordered list of certificates in which each certificate's signature is verified with the public key in the next one, beginning with the end-entity certificate being validated. It must end at a trust anchor, a CA key the relying party already trusts, normally a root certificate in its trust store.

2. Distinguish forward and reverse certificates, and show how Asha, certified by MUMBAI, obtains Bharat's key when Bharat is certified by PUNE and both CAs are certified by INDIA. A forward certificate of a CA is a certificate of that CA issued by another CA; a reverse certificate is one issued by that CA for another CA. Asha trusts MUMBAI's key, so she uses the reverse certificate MUMBAI<<INDIA>> to learn INDIA's key, then the forward certificate INDIA<<PUNE>> to learn PUNE's key, then PUNE<<Bharat>> to learn Bharat's key, verifying each signature with the key learned at the step before.

3. List the checks RFC 5280 requires for each certificate in a path. That its signature verifies with the public key of the certificate above it; that its validity period includes the current time; that it has not been revoked; and that its issuer name matches the subject of the certificate above it. For each certificate acting as an issuer, also that it is marked as a CA in its basic constraints, that any path length limit is respected, and that its key usage allows signing certificates.

4. Why is a root certificate trusted, if anyone can make a certificate that signs itself? Because it has been placed in the relying party's trust store by the browser or operating system maker. Its self-signature proves only that the copy has not been altered; the decision to trust it was made when it was put in the store.

5. Compare a CRL with OCSP. A CRL is a signed list of all revoked, unexpired certificates that the client downloads; it is complete but grows with every revocation and is only as fresh as its issue time. OCSP answers a question about one certificate with a signed good, revoked or unknown; it is small and fresh but tells the CA which site the user is visiting and fails when the responder is unreachable. In 2026 the CAs' Baseline Requirements require CRLs and make OCSP optional.

munotes.in423

Certificate Chains, Revocation and the CRL

6. Why is it said that revocation mostly does not work, and what has the industry done instead? Because revocation checks were soft-fail, so an attacker able to intercept a connection can also block the check; because online checks cost time and privacy, so Chrome does not generally perform them; and because a revocation is useless to a client that never learns of it. The industry has moved to pushing compact revocation data to browsers, CRLSets in Chrome and CRLite in Firefox, and above all to shorter certificate lifetimes, falling to a 47-day maximum from March 2029, so that a certificate with a stolen key expires soon whether or not anyone checks.

7. munotes.in's server sends a certificate for Root YE signed by ISRG Root X2. Why, when Root YE also has a self-signed certificate? Because Root YE is a new root that not every device yet trusts. The cross-certificate lets a device that trusts only the older roots build a path through X2 to X1, while a device that already trusts Root YE uses its self-signed certificate and stops earlier. Both certificates carry the same public key, so the signatures below Root YE verify either way.

Contents This chapter on its own page

munotes.in424

Chapter Sixty-Six

Public-Key Infrastructure

Syllabus topic Module 2, "Authentication Applications: Public-Key Infrastructure"

In one line

A public-key infrastructure is everything around the certificate: the authorities that issue it, the people who check who you are, the places certificates are published, and the rules and procedures that make a stranger's signature worth trusting.

In the words an answer should use, which are RFC 4949's: a PKI is "the set of hardware, software, people, policies, and procedures needed to create, manage, store, distribute, and revoke digital certificates based on asymmetric cryptography". The PKIX model built on X.509 recognises five components: end entities, certification authorities (CAs), registration authorities (RAs), CRL issuers and repositories.

Why "infrastructure"

The last two chapters treated a certificate as a finished object: here it is, verify it. A certificate had to come from somewhere. Someone checked that the person applying was who they said. Someone generated a key and kept the private half safe. Someone published the certificate where others could find it, and someone must be told the day the key is stolen.

The cryptography is the easy part. A signature takes milliseconds. Checking a person's identity, keeping a root key offline for twenty years, and running a revocation service are organisations, not algorithms, and together they are what the word infrastructure means.

The five components

RFC 5280 s.3 sets out the model that Internet PKI specifications assume.

ComponentWhat it doesExample from the last two chapters
End entitythe subject of a certificate, or a user of certificatesmunotes.in; a browser checking it
Certification authority (CA)issues certificates by signing them, and is responsible for saying which of them are revokedLet's Encrypt YE1
Registration authority (RA)an optional body to which a CA delegates some management work, above all checking identities; it signs nothingin India, the offices that check an applicant's documents for a licensed CA
CRL issuergenerates and signs revocation lists; usually the CA itself, but it may delegateYE1, signing list 17
Repositorystores certificates and CRLs and hands them outye1.i.lencr.org and ye1.c.lencr.org

The RA is the component examiners probe, because it is the one that is easy to misstate. RFC 4949 defines it as an entity that "does not sign either digital certificates or CRLs" but records or verifies the information, "particularly the identities of subjects", that a CA needs. The CA keeps the signing key and the signing decision; the RA does the legwork, which in a country the size of India is most of the work.

The management functions

RFC 5280 s.3.5 lists seven functions that a PKI's management protocols may have to support. Each is a step in a certificate's life.

FunctionWhat happens
Registrationthe user first makes itself known to a CA, directly or through an RA, before any certificate is issued
Initialisationthe user's system is given what it needs to operate: the public keys of the CAs it will trust, and usually its own key pair
Certificationthe CA issues a certificate for the user's public key, returns it, and may post it in a repository
Key pair recoverya backed-up private key, used for encryption, is restored after the user loses it
Key pair updatea key pair is replaced with a new one, with a new certificate, as all key pairs must be regularly
Revocation requestan authorised person tells the CA something is wrong and the certificate must be revoked
Cross-certificationtwo CAs exchange what they need to issue a cross-certificate, one CA certifying another's signing key
munotes.in425

Public-Key Infrastructure

Key pair recovery is for encryption keys only, and the reason is worth stating. A lost encryption key means data that can never be decrypted again, so a copy is kept. A signing key is never backed up by anyone but its owner: RFC 4949 notes that requiring a client to generate its own signature key pair "helps maintain system integrity", because then only the client ever possesses the private key. A signature is worth something only if nobody else could have made it.

How a CA says how it works

A certificate is only as good as the checks behind it, so a CA publishes them. A certificate policy says what a certificate may be relied on for; a certification practice statement (CPS) describes how the CA actually operates: how it checks identities, protects its keys and handles revocation. The certificate names its policy in the certificate policies extension, as munotes.in's names the CA/Browser Forum's domain-validated policy. India's Act writes the CPS into law: an application for a certificate under section 35(3) is accompanied by a certification practice statement.

Trust models: how separate PKIs come to trust each other

A PKI serving one organisation can have one root. The problem begins when two PKIs must trust each other. RFC 4158 describes the structures in use.

Hierarchical. One root CA, which certifies subordinate CAs, which certify end entities. Certificates are issued in one direction only and a CA never certifies one above it. Path building is simple: fetch each issuer's certificate until you reach the root. The trust list is the variation every browser uses: not one root but a list of them, "dozens to more than one hundred" in RFC 4158's words, any of which will do. RFC 4158 names its weakness plainly: the user "may have little or no idea of the policies or operating practices" of the roots, and the compromise of any one of them may compromise the whole system.

munotes.in426

Public-Key Infrastructure

Mesh. No single root. CAs are peers, and each cross-certifies the others, so each user starts from their own CA. Nothing is superior to anything else, and that is the problem: there are many paths between any two points, and a validator looking for one can wander through a very large number of them.

Bilateral cross-certification. Two existing PKIs join by having their roots certify each other. Workable for two or three; for many, the number of agreements grows with the square of their number.

Bridge. A bridge CA is a hub that issues no certificates to end users. Each PKI cross-certifies with the bridge alone, so every PKI can reach every other in two steps, and each new PKI adds one agreement, not one per existing member.

The fifth model, which gives up authorities altogether and lets users vouch for each other, is PGP's web of trust, the subject of the next three chapters.

India's PKI under the Information Technology Act

The PKI an Indian student will actually use is the one the IT Act 2000 creates for electronic signatures: the one behind every electronic signature certificate issued under the Act.

The Controller of Certifying Authorities. Section 17 lets the Central Government appoint a Controller, who works under its general control. Section 18 lists the Controller's functions, fourteen of them, and one is the root of the whole system: clause (b), "certifying public keys of the Certifying Authorities". The Controller's office describes what it built on that clause: it "has established the RCAI under section 18(b) of the IT Act to digitally sign the public keys of CAs in the country". RCAI is the Root Certifying Authority of India, the trust anchor for every Indian electronic signature certificate. Its root certificate "CCA India 2022", valid until 2042, is published on the Controller's website, where the Controller says the keys it signs "can be verified by a relying party".

The Certifying Authorities. Under section 21 any person may apply to the Controller for a licence to issue electronic signature certificates, and no licence issues unless the applicant meets prescribed requirements of qualification, expertise, manpower, finance and infrastructure. A licence is not transferable or heritable. On 30 September 2026 the Controller's disclosure-records page listed 23 licensed CAs, among them e-Mudhra, (n)Code Solutions, Capricorn, IDRBT, CDAC and the Indian Army, Navy and Air Force. Section 19 lets the Controller recognise a foreign CA, whose certificates are then valid under the Act.

The certificate's life, as the Act writes it.

  • Application, section 35: to a CA, with a fee not exceeding twenty-five thousand rupees and a certification practice statement or a statement of particulars; the CA may grant it, or reject it for reasons recorded in writing and only after giving the applicant a reasonable opportunity to show cause.
  • Representations, section 36: in issuing, the CA certifies among other things that the subscriber holds the private key matching the public key listed, that the two form a functioning key pair, and that the information in the certificate is accurate.
  • Key generation, section 40: the subscriber generates the key pair, by applying the security procedure. The private key is the subscriber's, not the CA's.
  • Acceptance, section 41: a subscriber accepts a certificate by publishing it or otherwise showing approval, and by accepting certifies to everyone who reasonably relies on it that they hold the private key and that their representations were true.
  • Suspension, section 37: on the subscriber's request, or in the public interest; not beyond fifteen days unless the subscriber has been heard.
  • Revocation, section 38: on request, on death, on the dissolution of a firm or company; or where a material fact was false, a requirement was not met, or the CA's own private key or security system was compromised; not without a hearing.
  • Notice, section 39: a suspension or revocation is published in the repository named in the certificate.
  • Control of the private key, section 42: the subscriber must take reasonable care to keep control of it, and if it is compromised must tell the CA without delay. The Explanation is the sentence to remember: the subscriber "shall be liable till he has informed the Certifying Authority that the private key has been compromised".
munotes.in427

Public-Key Infrastructure

eSign. For a person who does not own a token, the Controller's office describes an online service in which the user is authenticated by e-KYC, with a one-time password or a biometric, and a certificate is issued for a key pair made in a hardware security module, whose private key is "destroyed immediately after one time use". The Act makes room for techniques of this kind in section 3A, which lets an electronic authentication technique listed in its Second Schedule be used if it is reliable.

Mapped onto the PKIX model:

PKIX termIndian PKI under the IT Act
Trust anchor, root CARCAI, operated by the Controller, s.18(b)
Policy authoritythe Controller, who licenses CAs and lays down their standards, s.18 and s.21
CAa licensed Certifying Authority
End entitythe subscriber
Registration authoritythe CA's own identity-checking arrangements; the Act puts the duty on the CA
Repositorythe repository named in the certificate, s.39
Certificate holdsuspension, s.37
Revocationrevocation, s.38
munotes.in428

Public-Key Infrastructure

The run: a link of India's PKI, and the cost of each trust model

The two files are real: the Controller's root certificate, and the CA certificate it issued to eMudhra, one of the licensed CAs, both downloaded from the Controller's website.

-----BEGIN CERTIFICATE-----
MIIFNDCCAxygAwIBAgIQdiQz69smdlqFYM0KqC/hFzANBgkqhkiG9w0BAQsFADA6
MQswCQYDVQQGEwJJTjESMBAGA1UEChMJSW5kaWEgUEtJMRcwFQYDVQQDEw5DQ0Eg
SW5kaWEgMjAyMjAeFw0yMjAyMDIxMjA0MzdaFw00MjAyMDIxMjA0MzdaMDoxCzAJ
BgNVBAYTAklOMRIwEAYDVQQKEwlJbmRpYSBQS0kxFzAVBgNVBAMTDkNDQSBJbmRp
YSAyMDIyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv3EBudWC8HY0
oSwtJZCqpjQTGpEewl3EdDqUORV0qoFp78mdR/vuATXI83G7nF9RLvmNjgQgKr/b
Mx6gPO4Y57bMjAsgwEzleFclZka/sqc68iN5rS3huhrCX6MEINLyDOQ71MRA7GJC
aNL6E3j1438eTu011mlikeZYBdkhvfpAVjCw90w8wcWDmqx66Y561T/RiXyz2uEh
BBZAD43gV58eXStOeOTwAzEZYMrmp232GfmQKabYRfdIRus1avyuGea2nICEsRHE
8M2tdzwpGP7oIy2qHBFJJ+3AwmwQA4DjmDkJtCD+58awohQavRNhqjsGD+ZifG3V
R4i6WrKv8OWqZzcZj3g3Elr5+fRMlz1GSqkWPBw1Ev8KWTHazSUKF7OMxm3XzyXx
Qnw7fZF9GOVtx3adpfRPqYGgtbOP34EVkz4wsHvNMrvUrYcKymdOrnkTjlX26fIH
UJpKGYkLk9q0jhMNKs4Rn8lj4pJ7YF33/ND4bjpV0ex1EAQz0iZvT37OnxNiuAZ/
+4Djf075UuNX2ecWnadOrN1r8NAParZIwUoSUnWhU8TqAWWRqzFURHUZuOMQcA0g
eg4c9zqtBoUPgtQksbIAEsEXmDuRpwSIFjEkK11f5Eemfmfdg37KyIjQ67TRTmBA
+kT9Q5JIm/e7m1ILg/HKckgLUOCnAMsCAwEAAaM2MDQwDwYDVR0TAQH/BAUwAwEB
/zARBgNVHQ4ECgQITjtINlziX30wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB
CwUAA4ICAQCdbE8d1c1DysKtrtYlApYIXTlY3N2XHNQ6gKoaVWsKa1TJ/ovrT+FV
3bmQLet3aSoEG6pTe/vLZSg8WiF7cn7WuF4XlQS3yA2Uu8/cg/S4owqhQJp6K/Xg
6UoSBad9Kog1H8deOfV8Nmb8a89zB4Yf8/AepId+Lr/3I6O7iub+PUT2QBXnksa+
cf0yf+49GhyMCILZvctNSQd4Vxr9EgRvBARTrAgNQ9sEOJ6myOz4iTFR7T2pIFP8
Cp15e8jEVI1q4IuHu3XlwJNk9f5k3gbwrzoy9P5rP8voQU3u9wh62JZa9U63b+u/
Ur1tsKb5Lx0YUedtHvpIiIRurEPxumW0twjrx8TrAcXRrViSL7dsXAoYC0dXo154
EE8jBAzgIIur7tJizxgXDEn4i2pu8Yd615YML9ii5BooEJ2j6fQ0nzyPRmx1Egw2
Fjlgzzceai4TUOcaCKab86yyu5MZIp+BiPR840nw5MggbRgYH2nFRBA70toVm4VF
lbZs3reGmaICm4ST6R395OxYS1iYBm5kXm9tLb4pkIhUxrkgyuiwE+DsWceBjHAY
aXnCgUGKtiG9tfBMUw3fChoPb9L1yKdNof3zXDdTloMqEpO4BFrmjco8kt1v0LUQ
PhNZmQP4nqd4Hqx2384nPmWDXbQ+eePyxRteYGY0hJeDLVpyeYG8VQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFuzCCA6OgAwIBAgIQTL5zEXyk47GtDCm20KgYoTANBgkqhkiG9w0BAQsFADA6
MQswCQYDVQQGEwJJTjESMBAGA1UEChMJSW5kaWEgUEtJMRcwFQYDVQQDEw5DQ0Eg
SW5kaWEgMjAyMjAeFw0yMjAyMTYxMTE4MDdaFw0zMjAyMTYxMTE4MDdaMIG5MQsw
CQYDVQQGEwJJTjEYMBYGA1UEChMPZU11ZGhyYSBMaW1pdGVkMR0wGwYDVQQLExRD
ZXJ0aWZ5aW5nIEF1dGhvcml0eTEPMA0GA1UEERMGNTYwMTAzMRIwEAYDVQQIEwlL
YXJuYXRha2ExEjAQBgNVBAkTCUJlbmdhbHVydTEdMBsGA1UEMxMUM3JkIEZsb29y
LFNhaSBBcmNhZGUxGTAXBgNVBAMTEGUtTXVkaHJhIENBIDIwMjIwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCnXihXmxlwRQ6z7uB1WgTS9wnbt4517H31
AuBOaxjDSojqlWLtHp+D1uUaPNp51fDlUh0QUv/qaG1xh6dEjZEcgC4+w87xCCXP
wtEbgXiHWHENftiCxYmcrx3Sl7toev/g3Jb3+Ll5oJMf+cW4vmZxD9AmbtwcAVf4
AhcwwW6C+IsU29x/eyFzIPbDsFxTJeRwmpmULsZxyjHAwIKUZYDcFxCEGZHYJSFr
A2RFCex42LWzhAeMfggDdY9ANKLiCMl6kzG8g+h/cOZqtqHJXF8OuACuEGBWhzYn
4Fjncwv9sOlcJRguLtfv9RlACD2mTXZ+g7ZvOMYfJB/1coy70hZxAgMBAAGjggE7
MIIBNzASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBQE4Ll0fMCS/+9vxNY1
JP+6/RA6TDASBgNVHSAECzAJMAcGBWCCZGQCMBMGA1UdIwQMMAqACE47SDZc4l99
MIGABggrBgEFBQcBAQR0MHIwHgYIKwYBBQUHMAGGEmh0dHA6Ly9vY3ZzLmdvdi5p
bjBQBggrBgEFBQcwAoZEaHR0cDovL3d3dy5jY2EuZ292LmluL2NjYS9zaXRlcy9k
ZWZhdWx0L2ZpbGVzL2ZpbGVzL0NDQUluZGlhMjAyMi5jZXIwDgYDVR0PAQH/BAQD
AgEGMEYGA1UdHwQ/MD0wO6A5oDeGNWh0dHA6Ly9jY2EuZ292LmluL3J3L3Jlc291
cmNlcy9DQ0FJbmRpYTIwMjJMYXRlc3QuY3JsMA0GCSqGSIb3DQEBCwUAA4ICAQBP
r1EdMBFUv+BHJ9UMh0ERlsRHfrsh6WUD8pvF+d+HDvTy9KGaj6/Lgj6RmtCTgdOt
ODo+HMHEMqg0INQZcGVREHlk9quqDaN1qNqEyWN98PtWIQQqWqUZCFESEHYm2mXk
24h/OsjEIoO+5ZtPiN/l4u9RqWasqjnakGtCco7r3G6QNMFnbI5r2nBUDa4tQemI
E1wbxfzyt1VyCN7SPbfBKuMVgMjI2+ZZ9lZFCSahEoIDQeMwiXPGQFM/bZ8QrP97
sf2LkZivDhNT9QcgDyQQmfPhnO9u2M7aSuUX5tf9CDZusRdX/X4yanV3ceQ0Xgl6
/ozEnG1kCi8BR2SVS9Fsxs+vp5Gb7PCggQLqlV0hq3EGqYO8y6lmWHGoFHT012tR
DpjB4RdA4XhTfZ7ZfryrKe9nwyuh55r+FYFRzWIiV2xeWSmU1hlNjnUnVm8dEPdn
34Yua3YAnSHT9bslp9g2IOTc+a3pqfn5Vp4GGoISA7ZdG0Ot3Nzd0l6ERu1B7rcI
25qFqVNFAbAor1ux6VQJ5PmTrI3CYi3LdiEl6lBy8SD8QJvn5sL9TMvlUjYBBPYV
ji6dMmdXdzrfJG9ldWoOgZCehK4ZZcrWiRbo6WcqXKv1y6HqjNHNP78NhTXbVhU6
guAyDoUTbAmqG7B9i+7JZJoker2dN9x1+eMxWPoBuw==
-----END CERTIFICATE-----

The listing verifies the second against the first exactly as the previous chapter verified munotes.in's chain, RSA with SHA-256 from integers alone. Then it builds a mesh of k PKIs and a bridge joining k PKIs, and counts both the certificates each needs and every route a validator could follow from one PKI to another without visiting a CA twice.

# India's PKI, one real link verified; then what each trust structure costs, counted.
import base64, datetime, hashlib, itertools

def der(path):
    lines = open(path).read().split('\n')
    return base64.b64decode(''.join(l for l in lines if l and not l.startswith('-----')))

def tlv(d, i):
    tag, n, j = d[i], d[i + 1], i + 2
    if n & 0x80:
        k = n & 0x7F
        n, j = int.from_bytes(d[j:j + k], 'big'), j + k
    return tag, j, j + n

def items(d, s, e):
    out = []
    while s < e:
        t, a, b = tlv(d, s)
        out.append((t, a, b, s)); s = b
    return out

def oid(b):
    arcs, v = [b[0] // 40, b[0] % 40], 0
    for x in b[1:]:
        v = (v << 7) | (x & 0x7F)
        if not x & 0x80: arcs.append(v); v = 0
    return '.'.join(map(str, arcs))

def common_name(d, s, e):
    for _, s1, e1, _ in items(d, s, e):
        for _, s2, e2, _ in items(d, s1, e1):
            (_, a, b, _), (_, c, f, _) = items(d, s2, e2)
            if oid(d[a:b]) == '2.5.4.3':
                return d[c:f].decode()

def parse(path):
    d = der(path)
    _, s, e = tlv(d, 0)
    (_, ts, te, t0), _, (_, ss, se, _) = items(d, s, e)
    f = items(d, ts, te)
    v = items(d, f[4][1], f[4][2])
    spki = items(d, f[6][1], f[6][2])
    key = d[spki[1][1] + 1:spki[1][2]]
    (_, a, b, _), (_, x, y, _) = items(key, *tlv(key, 0)[1:])
    c = {'tbs': d[t0:te], 'issuer': common_name(d, f[3][1], f[3][2]),
         'subject': common_name(d, f[5][1], f[5][2]),
         'from': datetime.datetime.strptime(d[v[0][1]:v[0][2]].decode(), '%y%m%d%H%M%SZ').date(),
         'until': datetime.datetime.strptime(d[v[1][1]:v[1][2]].decode(), '%y%m%d%H%M%SZ').date(),
         'n': int.from_bytes(key[a:b], 'big'), 'e': int.from_bytes(key[x:y], 'big'),
         'sig': int.from_bytes(d[ss + 1:se], 'big'), 'ca': False, 'path': None}
    for _, xs, xe, _ in items(d, *tlv(d, f[7][1])[1:]):
        p = items(d, xs, xe)
        if oid(d[p[0][1]:p[0][2]]) == '2.5.29.19':
            inner = items(d, *tlv(d, p[-1][1])[1:])
            c['ca'] = bool(inner and inner[0][0] == 1 and d[inner[0][1]])
            c['path'] = next((int.from_bytes(d[a:b], 'big') for t, a, b, _ in inner if t == 2),
                             None)
    return c

def rsa_sha256_ok(cert, issuer):           # RFC 8017, RSASSA-PKCS1-v1_5 with SHA-256
    size = (issuer['n'].bit_length() + 7) // 8
    em = pow(cert['sig'], issuer['e'], issuer['n']).to_bytes(size, 'big')
    tail = bytes.fromhex('3031300d060960864801650304020105000420') + \
        hashlib.sha256(cert['tbs']).digest()
    return em == b'\x00\x01' + b'\xff' * (size - 3 - len(tail)) + b'\x00' + tail

root, ca = parse('cca-india-2022.pem'), parse('emudhra-ca-2022.pem')
print('INDIA\'S PKI: THE CONTROLLER\'S ROOT AND ONE LICENSED CA')
for c in (root, ca):
    limit = '' if c['path'] is None else ', at most %d CA below it' % c['path']
    print('  %-17s issued by %-15s %s to %s   RSA-%d   CA: %s%s' % (
        c['subject'], c['issuer'], c['from'], c['until'], c['n'].bit_length(),
        'yes' if c['ca'] else 'no', limit))
print('  the root signs itself                          :', rsa_sha256_ok(root, root))
print('  e-Mudhra\'s certificate verifies with the root  :', rsa_sha256_ok(ca, root))
forged = dict(ca, tbs=ca['tbs'].replace(b'e-Mudhra CA 2022', b'e-Mudhra CA 2023'))
assert forged['tbs'] != ca['tbs']
print('  the same, with its name changed by one digit   :', rsa_sha256_ok(forged, root))

# ---- trust structures ------------------------------------------------------------------------
def paths(graph, a, b):                   # every route from a to b that repeats no CA
    count, stack = 0, [(a, {a})]
    while stack:
        node, seen = stack.pop()
        for nxt in graph[node]:
            if nxt == b:
                count += 1
            elif nxt not in seen:
                stack.append((nxt, seen | {nxt}))
    return count

print()
print('JOINING k SEPARATE PKIs SO EACH TRUSTS THE OTHERS')
print('     k   mesh: certificates   routes A to B   bridge: certificates   routes A to B')
for k in (3, 4, 6, 8, 10):
    names = ['PKI%d' % i for i in range(k)]
    mesh = {x: [y for y in names if y != x] for x in names}
    bridge = {x: ['BRIDGE'] for x in names}
    bridge['BRIDGE'] = list(names)
    print('  %4d   %18d   %13s   %20d   %13d' % (
        k, sum(len(v) for v in mesh.values()), f"{paths(mesh, 'PKI0', 'PKI1'):,}",
        sum(len(v) for v in bridge.values()), paths(bridge, 'PKI0', 'PKI1')))
munotes.in429

Public-Key Infrastructure

INDIA'S PKI: THE CONTROLLER'S ROOT AND ONE LICENSED CA
  CCA India 2022    issued by CCA India 2022  2022-02-02 to 2042-02-02   RSA-4096   CA: yes
  e-Mudhra CA 2022  issued by CCA India 2022  2022-02-16 to 2032-02-16   RSA-2048   CA: yes, at most 1 CA below it
  the root signs itself                          : True
  e-Mudhra's certificate verifies with the root  : True
  the same, with its name changed by one digit   : False

JOINING k SEPARATE PKIs SO EACH TRUSTS THE OTHERS
     k   mesh: certificates   routes A to B   bridge: certificates   routes A to B
     3                    6               2                      6               1
     4                   12               5                      8               1
     6                   30              65                     12               1
     8                   56           1,957                     16               1
    10                   90         109,601                     20               1

What the run establishes, in order.

India's PKI is a hierarchy with a real root. CCA India 2022 is self-signed, with a 4,096-bit RSA key, valid for twenty years from February 2022. e-Mudhra CA 2022 is signed by it and verifies; its basic constraints allow one further level of CA below it.

munotes.in430

Public-Key Infrastructure

One digit changed and the verification fails, exactly as with a website's certificate. The cryptography of India's PKI and of the web's is the same.

A mesh multiplies routes; a bridge does not. Ten PKIs fully cross-certified need 10 × 9 = 90 cross-certificates and offer 109,601 different routes between two of them; the same ten joined through a bridge need 2 × 10 = 20 certificates and offer exactly one route. A validator in a mesh must search; in a bridge it cannot get lost.

Worked example: a digital signature certificate for Asha's company

Asha is a director of a company that must file a return electronically, and the filing must carry her electronic signature.

  1. Registration. She applies to a licensed CA under section 35, with the fee and the documents the CA's identity checks require. The identity checking is registration-authority work; the CA takes the signing decision.
  2. Key generation. Her key pair is generated on her side, under section 40, for example inside a cryptographic token, so that the private key never leaves it.
  3. Certification. The CA verifies her identity and her request and issues a certificate binding her name to her public key, signed by the CA's key, which is itself certified by RCAI. Its representations under section 36 are now the CA's legal responsibility.
  4. Acceptance. She accepts the certificate, and under section 41 certifies to anyone relying on it that she holds the private key.
  5. Use. She signs the return. The receiving portal verifies her signature with her public key, her certificate with the CA's, and the CA's with RCAI's root.
  6. Loss. Her token is stolen. Under section 42 she must tell the CA without delay, and she remains liable for signatures made with the key until she does. The CA revokes the certificate under section 38 and publishes the notice under section 39.

Distinctions that carry marks

Certification authorityRegistration authority
Signs certificates and CRLsyesnever
Checks the applicant's identitymayusually, that is its purpose
Required in a PKIyesoptional
HierarchyMeshBridge
Rootone (or a trust list)none; each user trusts its own CAnone; a hub issues no end-user certificates
Certificates to join k PKIsnot applicablek(k - 1), every pair both ways2k, each PKI with the bridge
Paths between two pointsonemany, growing explosivelyone, through the bridge
ExampleIndia's RCAI; browsers' trust listspeer organisationsa hub joining several organisations' PKIs
Suspension, IT Act s.37Revocation, IT Act s.38
Effecttemporarypermanent
Limitnot beyond 15 days without a hearingnot without a hearing
X.509 counterpartcertificateHoldrevocation with a reason code
munotes.in431

Public-Key Infrastructure

What beginners get wrong here

Saying the registration authority issues certificates. It never signs anything. It verifies identities for the CA.

Equating PKI with certificates. A certificate is one product of a PKI. The infrastructure is the people, policies and procedures that make the certificate trustworthy.

Thinking the CA keeps the subscriber's private signing key. Under section 40 the subscriber generates the key pair, and a signing key is never backed up by anyone else. Key pair recovery is for encryption keys.

Assuming a browser's roots and India's root are the same list. RCAI is the trust anchor the Controller operates for electronic signatures under the Act, and the Controller's site is where a relying party is directed to verify the keys it signs.

Forgetting the subscriber's liability. Until the subscriber tells the CA that the key is compromised, section 42 keeps them liable.

Quick revision

  • PKI (RFC 4949): hardware, software, people, policies and procedures to create, manage, store, distribute and revoke certificates.
  • Five PKIX components (RFC 5280 s.3): end entity, CA, RA (optional, signs nothing), CRL issuer, repository.
  • Seven management functions (s.3.5): registration, initialisation, certification, key pair recovery (encryption keys only), key pair update, revocation request, cross-certification.
  • Trust models (RFC 4158): hierarchy and the browsers' trust list; mesh; bilateral cross-certification; bridge. Ten PKIs: mesh 90 certificates and 109,601 routes; bridge 20 and one.
  • India: Controller (s.17); functions (s.18), including (b) certifying CAs' public keys, done by RCAI; licences (s.21); 23 licensed CAs (30 September 2026); root CCA India 2022.
  • Certificate's life under the Act: application (s.35), representations (s.36), suspension up to 15 days without hearing (s.37), revocation (s.38), notice in repository (s.39), key generated by the subscriber (s.40), acceptance (s.41), liability until the CA is told (s.42). eSign: e-KYC and a one-time key in an HSM.

Test yourself

1. Name the five components of the PKIX model and state what each does. The end entity, the subject or user of certificates; the certification authority, which issues and signs certificates and indicates their revocation status; the registration authority, an optional body to which a CA delegates management tasks such as verifying identities, and which signs nothing; the CRL issuer, which generates and signs revocation lists, usually the CA itself; and the repository, which stores certificates and CRLs and distributes them to end entities.

2. Distinguish a CA from an RA. The CA holds the signing key: it signs certificates and revocation lists and bears responsibility for them. The RA is optional and never signs either; it performs delegated work such as authenticating applicants' identities, assigning names, distributing tokens and receiving revocation reports, so that the CA can make its decisions on verified information.

munotes.in432

Public-Key Infrastructure

3. List the PKI management functions and explain why key pair recovery applies to encryption keys only. Registration, initialisation, certification, key pair recovery, key pair update, revocation request and cross-certification. Recovery exists because losing an encryption key makes data permanently unreadable, so a backup is kept; a signing key is not backed up by anyone but its owner, because a signature proves something only if nobody else could have produced it.

4. Compare hierarchical, mesh and bridge trust models. A hierarchy has one root that certifies subordinate CAs in one direction, so there is a single path from any certificate to the root; browsers use a variation with a list of trusted roots. In a mesh, CAs are peers that cross-certify each other and each user trusts its own CA; there are many paths between any two points, and path building can become very expensive. A bridge CA is a hub that each PKI cross-certifies with, so k PKIs need 2k certificates and there is one route between any two, through the bridge.

5. What is RCAI, and under which provision does it operate? It is the Root Certifying Authority of India, the trust anchor of the Indian PKI for electronic signature certificates. The Controller of Certifying Authorities established it under section 18(b) of the Information Technology Act 2000, which empowers the Controller to certify the public keys of the Certifying Authorities.

6. A subscriber's token holding the private key is stolen on Monday and reported to the CA on Thursday. Who bears the risk for signatures made in between, and what must the CA do? Under section 42 the subscriber must communicate the compromise to the CA without delay, and the Explanation makes the subscriber liable until they have informed the CA, so the risk up to Thursday is theirs. On being told, the CA revokes the certificate under section 38, after giving the subscriber an opportunity to be heard, and publishes notice of the revocation in the repository named in the certificate under section 39.

7. How long can an Indian certificate be suspended without a hearing? Not beyond fifteen days. Section 37 allows suspension on the subscriber's request or in the public interest, but a suspension longer than fifteen days requires that the subscriber has first been given an opportunity of being heard.

Contents This chapter on its own page

munotes.in433

Chapter Sixty-Seven

Pretty Good Privacy: The Five Services

Syllabus topic Module 2, "Electronic Mail Security: Pretty Good Privacy"

In one line

PGP lets you sign an email so the reader knows it is yours and untouched, and encrypt it so only the reader can open it, using nothing but public keys that you and your correspondents manage yourselves.

In the words an answer should use: Pretty Good Privacy (PGP), standardised as OpenPGP, provides five services for electronic mail and stored files: authentication by digital signature, confidentiality by symmetric encryption under a one-time session key that is itself encrypted with the recipient's public key, compression, e-mail compatibility by radix-64 conversion, and segmentation of long messages.

Where PGP comes from

PGP was written by Philip Zimmermann. His essay "Why I Wrote PGP" is dated June 1991. In it he explains that a United States Senate bill of that year, whose wording would have had communications systems "permit the government to obtain the plain text contents" of communications, "led me to publish PGP electronically for free that year, shortly before the measure was defeated".

It then became a standard, and the standard has been rewritten three times:

YearDocumentWhat it is
1996RFC 1991"PGP Message Exchange Formats", by Atkins, Stallings and Zimmermann
1998RFC 2440the first OpenPGP standard: PGP's formats opened to any implementer
2007RFC 4880the OpenPGP standard for seventeen years, and the one most textbooks follow
2024RFC 9580the current OpenPGP standard, published July 2024

OpenPGP is the standard; PGP is the original program. The free implementation most people meet is GnuPG, the gpg command.

Why email needed this

Email as designed has no security at all. A message passes through several servers on its way, any of which can read it; the "From" line is typed by the sender and can say anything; and nothing shows whether the text was altered on the way. And email is store-and-forward: the recipient is usually not online when the message is sent, so there is nobody to hold a live conversation with. That rules out the challenge-and-response protocols of the authentication chapters, and leaves exactly the one-way, public-key methods: a signature the recipient can check later, and encryption the recipient can undo later.

The five services

ServiceWhat it givesHow PGP does itAlgorithms, RFC 9580 (2024)
Authenticationproof of who sent it, and that it was not altereda hash of the message, signed with the sender's private keyEd25519 signatures, SHA2-256
Confidentialityonly the intended recipient can read itthe message encrypted with a random one-time session key; the session key encrypted with the recipient's public keyAES-128 in OCB mode; X25519 to protect the session key
Compressiona smaller message, and less redundancy for an attackerZIP (Deflate) or ZLIB, applied after signing and before encryptingZLIB recommended; compression optional
E-mail compatibilitysurvives mail systems that only carry textthe binary result converted to printable characters, radix-64now simply called base64
Segmentationa message too long for a mail system can still be sentthe output split into parts, and reassembled on receiptdropped from the current standard
munotes.in434

Pretty Good Privacy: The Five Services

Authentication

The sender computes a hash of the message, signs the hash with their private key, and attaches the signature. The receiver computes the hash again and checks the signature with the sender's public key. If it verifies, the message came from the holder of that private key and has not changed since.

The textbook's PGP did this with MD5 or SHA-1 and RSA or DSA. RFC 9580 requires Ed25519 and SHA2-256, says RSA keys "SHOULD NOT be generated", forbids generating DSA and Elgamal keys, and says implementations "SHOULD NOT create messages that require the use of SHA-1". A signature is the only thing an old message can still be checked by, which is why verification of old signatures stays permitted.

Confidentiality

Public-key encryption of a whole message would be slow. PGP does what every protocol since has done:

  1. Generate a random session key, used for this one message only. RFC 9580: "Each symmetric key is used only once, for a single object."
  2. Encrypt the message with it, using a fast symmetric cipher.
  3. Encrypt the session key with the recipient's public key, once for each recipient.
  4. Send the encrypted session keys first, then the encrypted message.

The receiver decrypts the session key with their private key, then the message with the session key. The expensive public-key operation is done on sixteen bytes, not on the whole message.

The original PGP used IDEA for the message and RSA for the session key. RFC 9580 requires AES-128, forbids encrypting with IDEA, Triple DES or CAST5, and requires X25519, the elliptic-curve Diffie-Hellman of RFC 7748, to protect the session key.

Both services together. The message is signed first, then the message and signature are encrypted together. RFC 9580 s.2.1 gives exactly this order: "First, a signature is generated for the message and attached to the message. Then, the message plus signature is encrypted."

Compression

PGP compresses the signed message before encrypting it. The two reasons for where it compresses are the most asked part of this topic, and the run below proves both.

Sign before compressing. The signature is made over the uncompressed message, for two reasons. First, the recipient can store the message and its signature for later checking without having to keep a compressed copy. Second, and decisively, compression is not deterministic across implementations: two compressors, or one compressor at two settings, produce different bytes from the same input. A signature over compressed bytes would verify only for a recipient whose software compressed identically.

munotes.in435

Pretty Good Privacy: The Five Services

Compress before encrypting. Encrypted data looks random and has no repetition left for a compressor to exploit, so compressing after encryption saves nothing. And compressing first removes redundancy from the plaintext before the cipher sees it, which leaves an attacker less structure to work with.

E-mail compatibility

A signed and encrypted message is arbitrary binary data, and many mail systems carry only printable text. PGP converts every three bytes into four printable characters from a set of 64: radix-64, now called base64. The output is one third larger. The next chapter works the conversion by hand.

A useful consequence the run shows: for ordinary text, compression saves far more than radix-64 costs, so the final message is usually smaller than the original.

Segmentation

Mail systems once limited a message's size, and PGP split output that was too long into parts, each armored separately, to be joined and processed on receipt. RFC 4880 did this with armor headers of the form BEGIN PGP MESSAGE, PART X/Y. RFC 9580 no longer defines them. Segmentation belongs in an answer about PGP's five services, as the textbook's PGP had it, and an answer that also says it has left the current standard is the better answer.

The order of operations

On sending:

message -> sign -> compress -> encrypt -> radix-64 -> segment

On receiving, exactly reversed:

reassemble -> radix-64 decode -> decrypt -> decompress -> verify signature -> message

A service that is not wanted is skipped: a message that is only signed is signed, compressed and converted; one that is only encrypted is compressed, encrypted and converted.

The run: PGP's five services, and the two ordering questions settled

The listing sends a 60-row internal-marks circular from a department to an examination section. The signature is Ed25519 and the session key is protected with X25519, the two algorithms RFC 9580 requires, and both are first checked against the vectors their RFCs print. The symmetric cipher is a stand-in for AES, built from SHA-256 with a MAC, so that decryption fails loudly if anything is wrong.

# PGP's five services as one pipeline, and why the steps come in the order they do.
import base64, hashlib, hmac, random, zlib

rng = random.Random(670)

# ---- Ed25519 signatures, RFC 8032 s.5.1 (checked against its TEST 1 below) ------------
P = 2**255 - 19
L = 2**252 + 27742317777372353535851937790883648493
D = -121665 * pow(121666, -1, P) % P
def sha512(b): return hashlib.sha512(b).digest()
def padd(A, B):
    x1, y1, z1, t1 = A; x2, y2, z2, t2 = B
    a = (y1 - x1) * (y2 - x2) % P; b = (y1 + x1) * (y2 + x2) % P
    c = 2 * t1 * t2 * D % P; d = 2 * z1 * z2 % P
    e, f, g, h = b - a, d - c, d + c, b + a
    return (e * f % P, g * h % P, f * g % P, e * h % P)
def pmul(k, A):
    Q = (0, 1, 1, 0)
    while k:
        if k & 1: Q = padd(Q, A)
        A = padd(A, A); k >>= 1
    return Q
def recover_x(y, sign):
    x2 = (y * y - 1) * pow(D * y * y + 1, -1, P) % P
    x = pow(x2, (P + 3) // 8, P)
    if (x * x - x2) % P: x = x * pow(2, (P - 1) // 4, P) % P
    if (x * x - x2) % P: return None
    if x & 1 != sign: x = P - x
    return x
gy = 4 * pow(5, -1, P) % P
gx = recover_x(gy, 0)
G = (gx, gy, 1, gx * gy % P)
def compress(A):
    x, y, z, _ = A; zi = pow(z, -1, P); x, y = x * zi % P, y * zi % P
    return int.to_bytes(y | ((x & 1) << 255), 32, 'little')
def decompress(s):
    y = int.from_bytes(s, 'little'); sign = y >> 255; y &= (1 << 255) - 1
    x = recover_x(y, sign)
    return None if x is None else (x, y, 1, x * y % P)
def expand(secret):
    h = sha512(secret); a = int.from_bytes(h[:32], 'little')
    a &= (1 << 254) - 8; a |= 1 << 254
    return a, h[32:]
def public_key(secret): return compress(pmul(expand(secret)[0], G))
def sign(secret, msg):
    a, prefix = expand(secret); A = compress(pmul(a, G))
    r = int.from_bytes(sha512(prefix + msg), 'little') % L
    R = compress(pmul(r, G))
    h = int.from_bytes(sha512(R + A + msg), 'little') % L
    return R + int.to_bytes((r + h * a) % L, 32, 'little')
def point_eq(A, B):
    return (A[0] * B[2] - B[0] * A[2]) % P == 0 and (A[1] * B[2] - B[1] * A[2]) % P == 0
def verify(public, msg, sig):
    A = decompress(public); R = decompress(sig[:32]); s = int.from_bytes(sig[32:], 'little')
    if A is None or R is None or s >= L: return False
    h = int.from_bytes(sha512(sig[:32] + public + msg), 'little') % L
    return point_eq(pmul(s, G), padd(R, pmul(h, A)))
# ---- X25519 key agreement, RFC 7748 s.5 (checked against its s.6.1 vector below) ------
def x25519(k, u):
    k = int.from_bytes(k, 'little'); k &= ~7; k &= ~(128 << 8 * 31); k |= 64 << 8 * 31
    u = int.from_bytes(u, 'little') & ((1 << 255) - 1)
    x1, x2, z2, x3, z3, swap = u, 1, 0, u, 1, 0
    for t in reversed(range(255)):
        bit = (k >> t) & 1; swap ^= bit
        if swap: x2, x3, z2, z3 = x3, x2, z3, z2
        swap = bit
        A = x2 + z2; AA = A * A; B = x2 - z2; BB = B * B; E = AA - BB
        C = x3 + z3; Dd = x3 - z3; DA = Dd * A; CB = C * B
        x3 = (DA + CB) ** 2 % P; z3 = x1 * (DA - CB) ** 2 % P
        x2 = AA * BB % P; z2 = E * (AA + 121665 * E) % P
    if swap: x2, x3, z2, z3 = x3, x2, z3, z2
    return (x2 * pow(z2, P - 2, P) % P).to_bytes(32, 'little')

nine = (9).to_bytes(32, 'little')
print('Ed25519 reproduces RFC 8032 TEST 1:',
      sign(bytes.fromhex('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'), b'')
      .hex().startswith('e5564300c360ac72'))
print('X25519 reproduces RFC 7748 s.6.1   :', x25519(
      bytes.fromhex('77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a'), nine)
      .hex().startswith('8520f0098930a754'))

def seal(key, data):                      # stand-in for AES: keystream from SHA-256, then MAC
    nonce = rng.randbytes(12)
    stream = b''.join(hashlib.sha256(key + nonce + i.to_bytes(4, 'big')).digest()
                      for i in range(len(data) // 32 + 1))
    body = nonce + bytes(a ^ b for a, b in zip(data, stream))
    return body + hmac.new(key, body, hashlib.sha256).digest()[:16]

def unseal(key, blob):
    body, tag = blob[:-16], blob[-16:]
    assert hmac.compare_digest(tag, hmac.new(key, body, hashlib.sha256).digest()[:16])
    nonce, data = body[:12], body[12:]
    stream = b''.join(hashlib.sha256(key + nonce + i.to_bytes(4, 'big')).digest()
                      for i in range(len(data) // 32 + 1))
    return bytes(a ^ b for a, b in zip(data, stream))

# ---- the message: an internal-marks circular, 60 rows ------------------------------------
lines = ['From: Head, Department of Computer Science',
         'To: Examination Section',
         'Subject: Internal marks, Cyber and Information Security, Semester V', '']
for roll in range(1, 61):
    t1, t2, a1, a2 = (rng.randint(4, 10) for _ in range(4))
    lines.append('Roll %03d  Test 1 %2d/10  Test 2 %2d/10  Assignments %2d/10  Internal %2d/20'
                 % (roll, t1, t2, a1, a2 // 2 + (t1 + t2) // 2))
message = '\n'.join(lines).encode()

asha_secret = rng.randbytes(32)            # the sender's signing key
asha_public = public_key(asha_secret)
exam_secret = rng.randbytes(32)            # the recipient's key-agreement key
exam_public = x25519(exam_secret, nine)

# ---- sending ------------------------------------------------------------------------------
print()
print('SENDING, in PGP\'s order')
signature = sign(asha_secret, message)                                    # 1 authentication
signed = signature + message
print('  1 sign            message %5d bytes, plus a %d-byte signature'
      % (len(message), len(signature)))
compressed = zlib.compress(signed, 9)                                     # 2 compression
print('  2 compress        %5d bytes, %.0f per cent of what went in'
      % (len(compressed), 100 * len(compressed) / len(signed)))
session_key = rng.randbytes(16)                                           # 3 confidentiality
body = seal(session_key, compressed)
ephemeral = rng.randbytes(32)
wrap_key = hashlib.sha256(x25519(ephemeral, exam_public)).digest()
key_block = x25519(ephemeral, nine) + seal(wrap_key, session_key)
packet = key_block + body
print('  3 encrypt         %5d bytes: the session key wrapped for the recipient (%d),'
      ' then the body' % (len(packet), len(key_block)))
armored = base64.b64encode(packet).decode()                               # 4 e-mail compatibility
armor_lines = [armored[i:i + 64] for i in range(0, len(armored), 64)]
print('  4 radix-64        %5d characters in %d lines of 64: %.3f times the bytes'
      % (len(armored), len(armor_lines), len(armored) / len(packet)))
LIMIT = 1000                                                              # 5 segmentation
parts = []
for line in armor_lines:
    if not parts or len(parts[-1]) + len(line) + 1 > LIMIT:
        parts.append('')
    parts[-1] += line + '\n'
print('  5 segment         %d parts, none over %d characters' % (len(parts), LIMIT))

# ---- receiving ----------------------------------------------------------------------------
print()
print('RECEIVING, in reverse')
joined = base64.b64decode(''.join(parts))
their_share, wrapped, body = joined[:32], joined[32:32 + 12 + 16 + 16], joined[32 + 44:]
unwrap_key = hashlib.sha256(x25519(exam_secret, their_share)).digest()
got = zlib.decompress(unseal(unseal(unwrap_key, wrapped), body))
print('  parts rejoined, radix-64 decoded, session key unwrapped, decrypted, decompressed')
print('  signature verifies with the sender\'s public key:',
      verify(asha_public, got[64:], got[:64]))
print('  the message is the one sent                  :', got[64:] == message)

# ---- why sign BEFORE compressing ----------------------------------------------------------
print()
print('WHY SIGN BEFORE COMPRESSING')
fast, best = zlib.compress(message, 1), zlib.compress(message, 9)
print('  the same message compressed two ways: %d and %d bytes, identical: %s'
      % (len(fast), len(best), fast == best))
sig_on_compressed = sign(asha_secret, best)
print('  a signature made over one compressed form verifies over the other:',
      verify(asha_public, fast, sig_on_compressed))
print('  a signature made over the message verifies after either round trip:',
      verify(asha_public, zlib.decompress(fast), signature)
      and verify(asha_public, zlib.decompress(best), signature))

# ---- why compress BEFORE encrypting -------------------------------------------------------
print()
print('WHY COMPRESS BEFORE ENCRYPTING')
encrypted_first = seal(session_key, message)
print('  compress, then encrypt: %5d bytes' % len(seal(session_key, zlib.compress(message, 9))))
print('  encrypt, then compress: %5d bytes, because ciphertext has no pattern left to squeeze'
      % len(zlib.compress(encrypted_first, 9)))
print('  plain message         : %5d bytes' % len(message))
munotes.in436

Pretty Good Privacy: The Five Services

Ed25519 reproduces RFC 8032 TEST 1: True
X25519 reproduces RFC 7748 s.6.1   : True

SENDING, in PGP's order
  1 sign            message  4455 bytes, plus a 64-byte signature
  2 compress          718 bytes, 16 per cent of what went in
  3 encrypt           822 bytes: the session key wrapped for the recipient (76), then the body
  4 radix-64         1096 characters in 18 lines of 64: 1.333 times the bytes
  5 segment         2 parts, none over 1000 characters

RECEIVING, in reverse
  parts rejoined, radix-64 decoded, session key unwrapped, decrypted, decompressed
  signature verifies with the sender's public key: True
  the message is the one sent                  : True

WHY SIGN BEFORE COMPRESSING
  the same message compressed two ways: 849 and 610 bytes, identical: False
  a signature made over one compressed form verifies over the other: False
  a signature made over the message verifies after either round trip: True

WHY COMPRESS BEFORE ENCRYPTING
  compress, then encrypt:   638 bytes
  encrypt, then compress:  4494 bytes, because ciphertext has no pattern left to squeeze
  plain message         :  4455 bytes
munotes.in437

Pretty Good Privacy: The Five Services

What the run establishes, in order.

munotes.in438

Pretty Good Privacy: The Five Services

Both primitives are the standard ones. Ed25519 reproduces RFC 8032's first test signature and X25519 reproduces the public key RFC 7748 prints.

munotes.in439

Pretty Good Privacy: The Five Services

The five services in order. The 4,455-byte message is signed; the message and signature compress to 718 bytes, 16 per cent of their size, because a table of marks repeats itself; encryption adds a 76-byte block holding the session key wrapped for the recipient; radix-64 makes it 1,096 characters, exactly four thirds of 822; segmentation splits the result into two parts of at most 1,000 characters. The message that finally travels, 1,096 characters, is a quarter of the size of the plain text.

Reception reverses every step and ends with a signature that verifies and a message identical to the one sent.

Why sign before compressing. The same message, compressed at two settings, gives 849 and 610 bytes: different bytes, both correct. A signature made over one compressed form does not verify over the other. A signature made over the message verifies whichever way the message was compressed in between.

Why compress before encrypting. Compressing and then encrypting gives 638 bytes. Encrypting and then compressing gives 4,494 bytes, more than the 4,455-byte original, because a good cipher's output leaves a compressor nothing to find.

PGP then and now

The textbook's PGPOpenPGP, RFC 9580 (2024)
SignatureRSA with MD5 or SHA-1; DSA with SHA-1Ed25519 required; RSA keys should not be generated; DSA must not
Session-key protectionRSA or ElgamalX25519 required
Message cipherIDEA, then CAST-128 or Triple DESAES-128 required, in OCB authenticated mode; IDEA, Triple DES, CAST5 must not encrypt
HashMD5, SHA-1SHA2-256 required
CompressionZIPoptional; ZLIB recommended
Printable encodingradix-64 with a CRC-24 checksum linebase64; the checksum should not be generated
Segmentationmulti-part armornot in the standard

Distinctions that carry marks

Session keyPublic and private keys
Kindsymmetricasymmetric
Lifetimeone messageyears
Made bythe sender's software, at random, for each messagethe user, once
Encryptsthe messagethe session key, for the recipient; or the hash, to sign
SigningEncrypting
Key used by the senderthe sender's private keythe recipient's public key
Key used by the receiverthe sender's public keythe receiver's private key
Serviceauthentication and integrityconfidentiality

What beginners get wrong here

Encrypting the whole message with the recipient's public key. Only the session key is encrypted that way. The message is encrypted with the session key.

munotes.in440

Pretty Good Privacy: The Five Services

Putting compression after encryption. Encrypted data does not compress, as the run shows: 4,494 bytes against 638.

Signing the compressed message. The signature is over the uncompressed message, because compressors disagree about bytes.

Swapping the keys. The sender signs with their own private key and encrypts with the recipient's public key. Signing with a public key or encrypting with one's own key is the commonest wrong answer.

Saying radix-64 is encryption. It is an encoding anyone can reverse. It exists only so binary data survives text-only mail.

Quick revision

  • PGP: Philip Zimmermann, published free in 1991; OpenPGP standards RFC 1991 (1996), RFC 2440 (1998), RFC 4880 (2007), RFC 9580 (2024); implementation GnuPG.
  • Five services: authentication (hash signed with the sender's private key); confidentiality (message under a one-time session key, session key under the recipient's public key); compression (ZIP); e-mail compatibility (radix-64, one third larger); segmentation (split and reassembled).
  • Order: sign, compress, encrypt, radix-64, segment; reverse on receipt.
  • Sign before compressing: compressors produce different bytes, so a signature over compressed data would not verify everywhere; and the plain message can be stored with its signature.
  • Compress before encrypting: ciphertext does not compress, and compression removes redundancy an attacker could use.
  • RFC 9580: Ed25519 and X25519 required; AES-128 and OCB required; SHA2-256 required; RSA keys should not be generated; DSA, Elgamal must not; IDEA, Triple DES, CAST5 must not encrypt; radix-64 renamed base64; segmentation dropped.

Test yourself

1. List the five services PGP provides, with the mechanism of each. Authentication, by a digital signature: a hash of the message signed with the sender's private key and checked with the sender's public key. Confidentiality, by encrypting the message with a one-time symmetric session key and encrypting that session key with the recipient's public key. Compression, by ZIP, after signing and before encrypting. E-mail compatibility, by radix-64 conversion of the binary result into printable characters. Segmentation, by splitting a message that is too long for a mail system into parts that are reassembled on receipt.

2. Why does PGP sign the message before compressing it? Because compression is not deterministic across implementations: different compressors, or one compressor at different settings, produce different bytes from the same message, so a signature over compressed bytes would verify only where the compression happened to match. Signing the uncompressed message also lets the recipient keep the plain message with its signature for later verification.

3. Why does PGP compress before encrypting rather than after? Encrypted data has no redundancy left, so compressing it saves nothing and can make it larger. Compressing first shrinks the message and removes the redundancy of the plaintext before encryption, which also makes cryptanalysis harder.

munotes.in441

Pretty Good Privacy: The Five Services

4. How does PGP make public-key encryption practical for long messages? It encrypts the message with a fast symmetric cipher under a random session key used only for that message, and applies the slow public-key operation only to the short session key, once for each recipient. The recipient recovers the session key with their private key and then decrypts the message.

5. Which keys does a sender use to sign and encrypt one message, and which does the recipient use to undo both? The sender signs with the sender's own private key and protects the session key with the recipient's public key. The recipient recovers the session key with the recipient's private key, decrypts the message, and verifies the signature with the sender's public key.

6. State three things RFC 9580 changed from the PGP described in textbooks. Any three of: it requires Ed25519 for signatures and X25519 for encryption, and says RSA keys should not be generated and DSA and Elgamal keys must not be; it forbids encrypting with IDEA, Triple DES or CAST5 and requires AES-128 with the OCB authenticated mode; it requires SHA2-256 and discourages SHA-1; it renames radix-64 as base64 and says the CRC-24 checksum should not be generated; and it no longer defines the multi-part armor that provided segmentation.

Contents This chapter on its own page

munotes.in442

Chapter Sixty-Eight

How a PGP Message Is Built, and Radix-64

Syllabus topic Module 2, "Electronic Mail Security: Pretty Good Privacy"

In one line

A PGP message is a stack of labelled boxes, each box a packet: one holding the session key for the recipient, one holding the signature, one holding the file itself; the stack is compressed, encrypted, and finally rewritten in 64 printable symbols so that email will carry it.

In the words an answer should use: a PGP message consists of a message component (the data, with its file name and a timestamp), an optional signature component (a timestamp, the key ID of the sender's public key, the leading two octets of the message digest, and the digest signed with the sender's private key), and, when it is encrypted, a session key component (the key ID of the recipient's public key and the session key encrypted with it); the signature and message components are compressed and encrypted with the session key, and the whole is converted to radix-64.

The textbook's picture of a PGP message

Read from the top, which is the order in which the recipient meets the parts:

SESSION KEY COMPONENT
    key ID of the recipient's public key          (says which private key opens it)
    session key Ks                                (encrypted with the recipient's public key)

SIGNATURE COMPONENT                               ---+
    timestamp                                        |
    key ID of the sender's public key                |  compressed with ZIP,
    leading two octets of the message digest         |  then encrypted with Ks
    message digest                                   |
        (the digest is encrypted with the sender's   |
         private key: this is the signature)         |
                                                     |
MESSAGE COMPONENT                                    |
    file name                                        |
    timestamp                                        |
    data                                          ---+

and the entire result is converted to radix-64

Every field answers a question the recipient will have.

  • Key ID of the recipient's public key. A person may hold several key pairs; this says which of their private keys opens the session key, without sending the whole public key.
  • Session key. The one-time key that decrypts everything below it.
  • Signature timestamp. When the signature was made.
  • Key ID of the sender's public key. Which public key to verify the signature with.
  • Leading two octets of the digest. The first sixteen bits of the hash, in the clear. The recipient hashes the message, compares these two bytes first, and so can tell immediately that it has the wrong key or a damaged message, before doing the expensive signature check.
  • Message digest. The hash of the message (and the signature timestamp), which is what the sender's private key signs.
  • File name and timestamp. So the recipient can save the data under its original name, and knows when the file was made.
  • Data. The message itself.

What the standard actually calls these parts: packets

The OpenPGP standard does not speak of components. It builds every message from packets. Each packet is a header, saying what kind of packet it is (its tag) and how long it is, followed by the body. A message is a sequence of packets, and a packet may contain other packets, which is how the "boxes" nest.

munotes.in443

How a PGP Message Is Built, and Radix-64

Textbook componentOpenPGP packet (RFC 9580)Tag
session key componentPublic-Key Encrypted Session Key packet1
signature componentSignature packet2
(a signature announced before the data, so it can be checked in one pass)One-Pass Signature packet4
the ZIP stepCompressed Data packet8
message componentLiteral Data packet: format, file name, date, data11
the encryption with KsSymmetrically Encrypted and Integrity Protected Data packet18

The mapping is exact where it matters. A Literal Data packet holds precisely the textbook's message component: a one-byte format, the file name, a four-byte date, and the data. A version 4 Signature packet holds the signature component's four fields, as the run below shows field by field: a creation time, the issuer's key ID, the left sixteen bits of the hash, and the signature values.

Radix-64

Radix-64 turns arbitrary bytes into printable characters. OpenPGP called it radix-64 for years; RFC 9580 simply calls it base64, which it always was.

The rule. Take the data three bytes at a time. Three bytes are 24 bits. Cut the 24 bits into four groups of six bits. Each six-bit group is a number from 0 to 63, and each number is written as one symbol from a fixed alphabet of 64:

ValuesSymbols
0 to 25A to Z
26 to 51a to z
52 to 610 to 9
62+
63/

Worked by hand. RFC 9580's sample signature packet begins with the three bytes 88, 5E and 04 in hexadecimal.

HexadecimalBinaryDecimal
8810001000136
5E0101111094
04000001004

Written one after another the 24 bits are 100010000101111000000100. Cut into sixes:

BinaryDecimalRadix-64 symbol
10001034i
0001015F
111000564
0001004E

So 88 5E 04 becomes iF4E, and that is exactly how the armored signature in RFC 9580 begins.

When the length is not a multiple of three. The last group is padded with zero bits and the missing symbols are written as =. One leftover byte gives two symbols and ==; two leftover bytes give three symbols and =. Every four symbols therefore still stand for exactly three bytes.

The cost. Four symbols for every three bytes is an increase of one third: the 96-byte signature packet becomes 128 symbols. Line breaks add a little more; RFC 9580 limits a line of base64 data to 76 characters.

munotes.in444

How a PGP Message Is Built, and Radix-64

Armor. Radix-64 data is wrapped in ASCII armor: a header line such as -----BEGIN PGP MESSAGE-----, optional header lines such as Version:, a blank line, the base64 lines, and a matching -----END PGP MESSAGE----- line. RFC 4880 added a checksum line: a 24-bit CRC of the decoded bytes, itself base64-encoded after an = sign.

The checksum is on its way out. RFC 9580 s.6.1 says the CRC-24 footer "SHOULD NOT be generated" except for compatibility, and must not be rejected when present, missing or wrong. Its reason: computing it "incurs a significant cost, while providing no meaningful integrity protection". A CRC catches transmission errors, not tampering, and the signature and the encryption's own integrity check already detect both.

The run: two published messages, taken apart

The listing works radix-64 by hand and against Python's own base64, then takes apart two messages published in the OpenPGP standards: the armored example of RFC 4880 s.6.6, and the sample version 4 signature of RFC 9580 Appendix A.2, which it verifies with the sample key of A.1.

# Inside two published OpenPGP messages: radix-64 by hand, the packets, and the signature.
import base64, datetime, hashlib, zlib

ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

def radix64(data):                         # three bytes in, four symbols out
    out = []
    for i in range(0, len(data), 3):
        chunk = data[i:i + 3]
        n = int.from_bytes(chunk + bytes(3 - len(chunk)), 'big')
        symbols = [ALPHABET[(n >> shift) & 63] for shift in (18, 12, 6, 0)]
        out += symbols[:len(chunk) + 1] + ['='] * (3 - len(chunk))
    return ''.join(out)

def crc24(data):                           # RFC 9580 s.6.1.1, translated from its C
    crc = 0xB704CE
    for byte in data:
        crc ^= byte << 16
        for _ in range(8):
            crc <<= 1
            if crc & 0x1000000:
                crc ^= 0x864CFB
    return crc & 0xFFFFFF

def dearmor(text):
    lines = [l for l in text.strip().split('\n') if l and not l.startswith('-----')]
    body = [l for l in lines if ':' not in l and not l.startswith('=')]
    check = [l[1:] for l in lines if l.startswith('=')]
    return base64.b64decode(''.join(body)), check

def packets(data):                         # (tag, body) per packet; one-byte lengths only,
                                           # which is all these two small messages use
    out, i = [], 0
    while i < len(data):
        head = data[i]
        if head & 0x40:                    # OpenPGP format: tag in the low six bits
            tag, n, i = head & 0x3F, data[i + 1], i + 2
        else:                              # legacy format: tag in bits 5 to 2
            tag, n, i = (head >> 2) & 0x0F, data[i + 1], i + 2
        out.append((tag, data[i:i + n]))
        i += n
    return out

# ---- 1. radix-64, the first three bytes by hand -----------------------------------------
sig_armor = """-----BEGIN PGP SIGNATURE-----

iF4EABYIAAYFAlX5X5UACgkQjP3hIZeWWpr2IgD/VvkMypjiECY3vZg/2xbBMd/S
ftgr9N3lYG4NdWrtM2YBANCcT6EVJ/A44PV/IgHYLy6iyQMyZfps60iehUuuYbQE
-----END PGP SIGNATURE-----"""
sig_bytes, _ = dearmor(sig_armor)
first = sig_bytes[:3]
bits = ''.join(format(b, '08b') for b in first)
print('RADIX-64, BY HAND')
print('  three bytes          ', ' '.join('%02X' % b for b in first))
print('  as 24 bits           ', ' '.join(bits[i:i + 8] for i in (0, 8, 16)))
print('  cut into four sixes  ', ' '.join(bits[i:i + 6] for i in (0, 6, 12, 18)))
print('  as numbers           ', ' '.join(str(int(bits[i:i + 6], 2)) for i in (0, 6, 12, 18)))
print('  as symbols           ', ' '.join(ALPHABET[int(bits[i:i + 6], 2)] for i in (0, 6, 12, 18)))
print('  this program and Python\'s base64 agree on all %d bytes:' % len(sig_bytes),
      radix64(sig_bytes) == base64.b64encode(sig_bytes).decode())
print('  %d bytes became %d symbols, %.4f times as many' % (
      len(sig_bytes), len(radix64(sig_bytes)), len(radix64(sig_bytes)) / len(sig_bytes)))
for n in (1, 2, 3, 4, 5):
    print('  %d byte(s) -> %s' % (n, radix64(bytes(range(65, 65 + n)))))

# ---- 2. RFC 4880 s.6.6: an armored message, taken apart ---------------------------------
msg_armor = """-----BEGIN PGP MESSAGE-----
Version: OpenPrivacy 0.99

yDgBO22WxBHv7O8X7O/jygAEzol56iUKiXmV+XmpCtmpqQUKiQrFqclFqUDBovzS
vBSFjNSiVHsuAA==
=njUN
-----END PGP MESSAGE-----"""
data, check = dearmor(msg_armor)
print()
print('RFC 4880\'S EXAMPLE MESSAGE, TAKEN APART')
print('  checksum line =%s, CRC-24 of the %d decoded bytes gives =%s'
      % (check[0], len(data), base64.b64encode(crc24(data).to_bytes(3, 'big')).decode()))
tag, body = packets(data)[0]
print('  outer packet: tag %d, Compressed Data; algorithm %d, which is ZIP' % (tag, body[0]))
inner = zlib.decompress(body[1:], -15)
tag, lit = packets(inner)[0]
name_len = lit[1]
print('  inside it  : tag %d, Literal Data' % tag)
print('    format   ', repr(chr(lit[0])), '(binary)')
print('    file name', repr(lit[2:2 + name_len].decode()))
print('    date     ', int.from_bytes(lit[2 + name_len:6 + name_len], 'big'))
print('    data     ', repr(lit[6 + name_len:].decode()))

# ---- 3. RFC 9580 A.2: a version 4 signature, field by field -----------------------------
tag, s = packets(sig_bytes)[0]
hashed_len = int.from_bytes(s[4:6], 'big')
hashed = s[6:6 + hashed_len]
u = 6 + hashed_len
unhashed_len = int.from_bytes(s[u:u + 2], 'big')
unhashed = s[u + 2:u + 2 + unhashed_len]
left16 = s[u + 2 + unhashed_len:u + 4 + unhashed_len]
mpis = s[u + 4 + unhashed_len:]
created = int.from_bytes(hashed[2:6], 'big')
print()
print('RFC 9580\'S SAMPLE SIGNATURE PACKET, FIELD BY FIELD (%d bytes)' % len(sig_bytes))
print('  packet tag           %d, Signature' % tag)
print('  version              %d' % s[0])
print('  signature type       0x%02X, a signature of a binary document' % s[1])
print('  public-key algorithm %d, EdDSA (legacy form)' % s[2])
print('  hash algorithm       %d, SHA2-256' % s[3])
print('  hashed subpacket     type %d, creation time %s UTC' % (
      hashed[1], datetime.datetime.fromtimestamp(created, datetime.timezone.utc)
      .strftime('%Y-%m-%d %H:%M:%S')))
print('  unhashed subpacket   type %d, issuer key ID %s'
      % (unhashed[1], unhashed[2:].hex().upper()))
print('  left 16 bits of hash %s' % left16.hex())
print('  signature            two numbers, r and s, %d bytes' % len(mpis))

trailer = s[:6 + hashed_len]
m = b'OpenPGP' + trailer + b'\x04\xff' + len(trailer).to_bytes(4, 'big')
d = hashlib.sha256(m).digest()
print('  hashed input rebuilt equals RFC 9580\'s m:',
      m.hex() == '4f70656e504750040016080006050255f95f9504ff0000000c')
print('  its SHA2-256 begins with the packet\'s   :', d[:2].hex(), d[:2] == left16)

# ---- Ed25519 verification, RFC 8032 s.5.1 ---------------------------------------------
P = 2**255 - 19
L = 2**252 + 27742317777372353535851937790883648493
D = -121665 * pow(121666, -1, P) % P
def sha512(b): return hashlib.sha512(b).digest()
def padd(A, B):
    x1, y1, z1, t1 = A; x2, y2, z2, t2 = B
    a = (y1 - x1) * (y2 - x2) % P; b = (y1 + x1) * (y2 + x2) % P
    c = 2 * t1 * t2 * D % P; d = 2 * z1 * z2 % P
    e, f, g, h = b - a, d - c, d + c, b + a
    return (e * f % P, g * h % P, f * g % P, e * h % P)
def pmul(k, A):
    Q = (0, 1, 1, 0)
    while k:
        if k & 1: Q = padd(Q, A)
        A = padd(A, A); k >>= 1
    return Q
def recover_x(y, sign):
    x2 = (y * y - 1) * pow(D * y * y + 1, -1, P) % P
    x = pow(x2, (P + 3) // 8, P)
    if (x * x - x2) % P: x = x * pow(2, (P - 1) // 4, P) % P
    if (x * x - x2) % P: return None
    if x & 1 != sign: x = P - x
    return x
gy = 4 * pow(5, -1, P) % P
gx = recover_x(gy, 0)
G = (gx, gy, 1, gx * gy % P)
def compress(A):
    x, y, z, _ = A; zi = pow(z, -1, P); x, y = x * zi % P, y * zi % P
    return int.to_bytes(y | ((x & 1) << 255), 32, 'little')
def decompress(s):
    y = int.from_bytes(s, 'little'); sign = y >> 255; y &= (1 << 255) - 1
    x = recover_x(y, sign)
    return None if x is None else (x, y, 1, x * y % P)
def expand(secret):
    h = sha512(secret); a = int.from_bytes(h[:32], 'little')
    a &= (1 << 254) - 8; a |= 1 << 254
    return a, h[32:]
def public_key(secret): return compress(pmul(expand(secret)[0], G))
def sign(secret, msg):
    a, prefix = expand(secret); A = compress(pmul(a, G))
    r = int.from_bytes(sha512(prefix + msg), 'little') % L
    R = compress(pmul(r, G))
    h = int.from_bytes(sha512(R + A + msg), 'little') % L
    return R + int.to_bytes((r + h * a) % L, 32, 'little')
def point_eq(A, B):
    return (A[0] * B[2] - B[0] * A[2]) % P == 0 and (A[1] * B[2] - B[1] * A[2]) % P == 0
def verify(public, msg, sig):
    A = decompress(public); R = decompress(sig[:32]); s = int.from_bytes(sig[32:], 'little')
    if A is None or R is None or s >= L: return False
    h = int.from_bytes(sha512(sig[:32] + public + msg), 'little') % L
    return point_eq(pmul(s, G), padd(R, pmul(h, A)))

q = bytes.fromhex('403f098994bdd916ed4053197934e4a87c80733a1280d62f8010992e43ee3b2406')
r = mpis[2:34]
s_ = mpis[36:68]
print('  Ed25519 verifies with the key of RFC 9580 A.1:', verify(q[1:], d, r + s_))
print('  and fails for the text "OpenPGP!"            :',
      verify(q[1:], hashlib.sha256(b'OpenPGP!' + m[7:]).digest(), r + s_))
munotes.in445

How a PGP Message Is Built, and Radix-64

RADIX-64, BY HAND
  three bytes           88 5E 04
  as 24 bits            10001000 01011110 00000100
  cut into four sixes   100010 000101 111000 000100
  as numbers            34 5 56 4
  as symbols            i F 4 E
  this program and Python's base64 agree on all 96 bytes: True
  96 bytes became 128 symbols, 1.3333 times as many
  1 byte(s) -> QQ==
  2 byte(s) -> QUI=
  3 byte(s) -> QUJD
  4 byte(s) -> QUJDRA==
  5 byte(s) -> QUJDREU=

RFC 4880'S EXAMPLE MESSAGE, TAKEN APART
  checksum line =njUN, CRC-24 of the 58 decoded bytes gives =njUN
  outer packet: tag 8, Compressed Data; algorithm 1, which is ZIP
  inside it  : tag 11, Literal Data
    format    'b' (binary)
    file name '_CONSOLE'
    date      0
    data      "Can't anyone keep a secret around here?\n"

RFC 9580'S SAMPLE SIGNATURE PACKET, FIELD BY FIELD (96 bytes)
  packet tag           2, Signature
  version              4
  signature type       0x00, a signature of a binary document
  public-key algorithm 22, EdDSA (legacy form)
  hash algorithm       8, SHA2-256
  hashed subpacket     type 2, creation time 2015-09-16 12:24:53 UTC
  unhashed subpacket   type 16, issuer key ID 8CFDE12197965A9A
  left 16 bits of hash f622
  signature            two numbers, r and s, 68 bytes
  hashed input rebuilt equals RFC 9580's m: True
  its SHA2-256 begins with the packet's   : f622 True
  Ed25519 verifies with the key of RFC 9580 A.1: True
  and fails for the text "OpenPGP!"            : False
munotes.in446

How a PGP Message Is Built, and Radix-64

What the run establishes, in order.

munotes.in447

How a PGP Message Is Built, and Radix-64

Radix-64 is exactly the rule above. 88 5E 04 becomes the six-bit numbers 34, 5, 56 and 4, and the symbols i, F, 4 and E. The program's own encoder agrees with Python's base64 on all 96 bytes, and 96 bytes become 128 symbols, 1.3333 times as many. The five short examples show the padding: one leftover byte ends in ==, two in =.

RFC 4880's example message comes apart into the textbook's message component. Its checksum line =njUN is exactly the CRC-24 the program computes. The outer packet is tag 8, Compressed Data, algorithm 1, ZIP; decompressed, it holds a tag 11 Literal Data packet whose fields are a format (b, binary), a file name (_CONSOLE), a date (0) and the data, the sentence "Can't anyone keep a secret around here?".

munotes.in448

How a PGP Message Is Built, and Radix-64

RFC 9580's sample signature carries the signature component's four fields. Version 4, a signature of a binary document, EdDSA, SHA2-256; a hashed subpacket giving the creation time, 16 September 2015 at 12:24:53 UTC; an unhashed subpacket giving the issuer's key ID, 8CFDE12197965A9A; the left sixteen bits of the hash, f622; and the signature values.

The digest is rebuilt and the signature verified. The program rebuilds the exact bytes that were hashed, the data OpenPGP followed by the signature's own hashed fields and a trailer, and they equal the value RFC 9580 prints. Their SHA2-256 begins with f622, the two octets the packet carries in the clear. The Ed25519 signature verifies with the sample key, and fails for the text OpenPGP!.

Why the creation time is hashed and the key ID is not. The creation time is inside the hashed subpackets, so it is covered by the signature and cannot be changed. The issuer key ID is in the unhashed area: it is only a hint about which key to use, and changing it could only make verification fail, never make a forgery pass.

Distinctions that carry marks

Radix-64Encryption
Purposecarry binary data through text-only mailkeep the content secret
Keynonethe session key
Reversible byanyoneonly the key holder
Sizeone third largerabout the same
Leading two octets of the digestThe signature
Sentin the clearas numbers only the sender's private key could produce
Provesnothing; it is a quick first checkthe sender's identity and the message's integrity
Cost to checka comparison of two bytesa public-key verification
Hashed subpacketUnhashed subpacket
Covered by the signatureyesno
Examplecreation timeissuer key ID
If alteredthe signature failsat worst, verification fails; no forgery results

What beginners get wrong here

Calling radix-64 encryption. It is an encoding that anyone can reverse, and it hides nothing.

Getting the expansion backwards. Three bytes become four symbols: the encoded form is one third larger, not one quarter.

Thinking the two leading octets are a security feature. They are sent in the clear and prove nothing; they only let the recipient abandon a wrong key or a damaged message cheaply.

Forgetting the file name and timestamp in the message component. They are part of the Literal Data packet, as RFC 4880's example shows.

Treating the CRC-24 line as integrity protection. It detects accidental damage, and RFC 9580 now says it should not be generated at all.

Quick revision

  • Message component: file name, timestamp, data (a Literal Data packet, tag 11).
  • Signature component: timestamp, key ID of the sender's public key, leading two octets of the digest, the digest signed with the sender's private key (a Signature packet, tag 2).
  • Session key component: key ID of the recipient's public key, session key encrypted with it (tag 1).
  • Signature and message are compressed (tag 8), encrypted with the session key (tag 18), and the whole is converted to radix-64.
  • Radix-64: 3 bytes, 24 bits, 4 groups of 6, 4 symbols from A-Z a-z 0-9 + /; = pads; one third larger; lines at most 76 characters.
  • Worked: 88 5E 04 is iF4E. RFC 4880's checksum =njUN is a CRC-24, which RFC 9580 says should not be generated.
munotes.in449

How a PGP Message Is Built, and Radix-64

Test yourself

1. Draw the general format of a PGP message and state what each field is for. The session key component holds the key ID of the recipient's public key, saying which of the recipient's private keys to use, and the session key encrypted with that public key. The signature component holds a timestamp, the key ID of the sender's public key, the leading two octets of the message digest as a quick check, and the digest encrypted with the sender's private key, which is the signature. The message component holds the file name, a timestamp and the data. The signature and message components are compressed and encrypted with the session key, and the whole message is converted to radix-64.

2. Convert the bytes 88 5E 04 (hexadecimal) to radix-64, showing the working. In binary the three bytes are 10001000, 01011110 and 00000100, which run together as the 24 bits 100010000101111000000100. Cut into four groups of six they are 100010, 000101, 111000 and 000100, which are 34, 5, 56 and 4. In the radix-64 alphabet 34 is i, 5 is F, 56 is 4 and 4 is E, so the result is iF4E.

3. By how much does radix-64 enlarge data, and why does PGP use it anyway? Every three bytes become four symbols, an increase of one third, plus a little for line breaks. PGP uses it because signed and encrypted output is arbitrary binary data and many mail systems carry only printable text; and since the message was compressed first, the result is usually still smaller than the original text.

4. Why does the signature carry the leading two octets of the message digest? So that the recipient can compare them with the first two bytes of the digest it computes and detect a wrong key or a damaged message immediately, before performing the costly public-key verification. They are sent in the clear and provide no security by themselves.

5. What does RFC 9580 say about the CRC-24 checksum, and why? It says the CRC-24 footer should not be generated except for compatibility with implementations that require it, and that a receiver must not reject a message whose footer is present, missing or wrong. The reason it gives is that computing the checksum costs time while providing no meaningful integrity protection, since a CRC detects accidental errors but not deliberate tampering.

munotes.in450

How a PGP Message Is Built, and Radix-64

6. In RFC 9580's sample signature, which field is protected by the signature and which is not, and why does it matter? The creation time is in the hashed subpackets, so it is covered by the signature and cannot be altered without the signature failing. The issuer key ID is in the unhashed area; it is only a hint about which key to try, and altering it could only cause verification to fail, never make a forged signature verify.

Contents This chapter on its own page

munotes.in451

Chapter Sixty-Nine

PGP Key Management and the Web of Trust

Syllabus topic Module 2, "Electronic Mail Security: Pretty Good Privacy"

In one line

In PGP each user is their own certificate authority: you decide whose keys you believe, and you can let people you trust vouch for keys you have never checked yourself; that network of vouching is the web of trust.

In the words an answer should use: PGP key management identifies keys by key IDs derived from their fingerprints, stores them in a private key ring (the user's own key pairs, the private keys encrypted under a passphrase) and a public key ring (the public keys of others, each with an owner trust, a computed key legitimacy and its signatures), and decides which public keys are valid by the web of trust, in which keys signed by sufficiently trusted introducers are accepted without a central authority.

The four jobs of key management in PGP

PGP uses three kinds of key: one-time session keys, each user's public and private keys, and passphrase-based keys that protect the private keys on disk. Managing them means four jobs.

  1. Make session keys nobody can predict. Each message gets a fresh random key; RFC 9580 devotes a security section to random number generation because a predictable session key breaks everything above it.
  2. Let a user have several key pairs, and say in each message which one was used, without sending the whole key: key IDs.
  3. Keep one's own private keys safe and everyone else's public keys organised: key rings, with private keys encrypted under a passphrase.
  4. Decide whose public key is really whose, without a certification authority: the web of trust.

Fingerprints and key IDs

A public key is long. A fingerprint is a hash of it, short enough to read aloud; a key ID is a shorter piece of the fingerprint, used as a label inside messages.

Key versionFingerprintKey ID
version 4SHA-1 of the byte 0x99, a 2-byte length and the public key packet: 160 bitsthe last 64 bits of the fingerprint
version 6 (RFC 9580)SHA2-256 of the byte 0x9B, a 4-byte length and the public key packet: 256 bitsthe first 64 bits of the fingerprint

Because the creation time is part of the packet that is hashed, the same key material created at a different moment has a different fingerprint and a different key ID. RFC 9580 warns plainly that implementations "SHOULD NOT assume that Key IDs are unique", and that a fingerprint is far more likely to be unique than a key ID.

Checking a key by fingerprint is how PGP users authenticate a key without any authority: Asha reads Bharat the fingerprint of the key she holds for him, over the phone or face to face, and he confirms it is his.

munotes.in452

PGP Key Management and the Web of Trust

The key rings

The textbook describes two tables that every PGP user keeps.

The private key ring holds the user's own key pairs:

FieldPurpose
Timestampwhen the key pair was made
Key IDto find the key named in a message
Public keythe public half
Encrypted private keythe private half, encrypted under a key derived from the passphrase
User IDusually the owner's email address

The public key ring holds everyone else's public keys, and it is where trust lives:

FieldPurpose
Timestamp, key ID, public key, user IDas above, for another person's key
Owner trusthow far you trust this key's owner to check other people's keys before signing them
Key legitimacyhow sure you are that this key really belongs to this user: computed, not set
Signaturesthe signatures other users have placed on this key
Signature trustfor each signature, the owner trust of whoever made it

GnuPG, today's common implementation, keeps the same information in different files, but the fields and the separation between trust you assign and validity that is computed are unchanged.

Protecting the private key: the passphrase

The private key is stored encrypted. When the user needs it, PGP asks for the passphrase, turns it into a key with a string-to-key (S2K) function, and decrypts the private key for as long as it is needed.

A good S2K is deliberately slow, because an attacker who steals the key file will try passphrases offline. RFC 9580 lists the methods: Simple (one hash of the passphrase, which "MUST NOT" be generated any more), Salted, Iterated and Salted (the salt and passphrase fed repeatedly into a hash until a set number of bytes has been hashed) and Argon2, which the RFC recommends because it needs a lot of memory as well as time, which makes guessing on specialised hardware expensive.

The web of trust

A certification authority tells everyone which keys to believe. PGP has none. Each user decides, and PGP turns those decisions into a calculation.

Two different questions, kept separate.

  • Owner trust: do I trust Bharat to check other people's identities carefully before he signs their keys? Set by me, private to me, never exported with the key. GnuPG's levels are unknown, none, marginal and full, and its scale has one more, ultimate, which it gives a user's own key: a key made with GnuPG 2.4.9 is listed as [ultimate] the moment it is created.
  • Key validity (the textbook's key legitimacy): am I sure that this key belongs to the person named on it? Computed from the signatures on the key and my owner trust in whoever made them.
munotes.in453

PGP Key Management and the Web of Trust

GnuPG's rule, with its default numbers. A key is valid if both hold:

  1. It is signed by enough valid keys: signed by you personally, or by one fully trusted key, or by three marginally trusted keys; and
  2. the path of signed keys from it back to your own key is five steps or shorter.

GnuPG's options --completes-needed, --marginals-needed and --max-cert-depth hold those three numbers, with defaults 1, 3 and 5. The textbook states the same idea with the numbers left general: some number of fully trusted signatures, or a larger number of partly trusted ones.

Why trust does not simply pass along. Asha signs Bharat's key: Bharat's key is valid to her. But unless she also trusts Bharat as an introducer, his signatures on other keys count for nothing to her. Validity says the key is his; owner trust says his judgement is good. Only the combination extends the web.

Revoking a key

If a private key is lost or stolen, its owner issues a revocation certificate, a signature by the key on itself declaring it revoked, and distributes it as widely as the key was. Because making one needs the private key, it must be made while the key is still in hand, and kept safe, so that a key whose private half is later lost can still be revoked. GnuPG now does this unasked: creating a key with GnuPG 2.4.9 stores a revocation certificate for it at once, in a folder named openpgp-revocs.d.

What happened to the web of trust

The web of trust needed a way to share keys and signatures, and for years that was a network of public keyservers that accepted any signature from anyone. In 2019 attackers exploited exactly that openness, attaching floods of fake signatures to real keys until software could not handle them. GnuPG's response, version 2.2.17 of 9 July 2019, was to "Ignore all key-signatures received from keyservers", because of "keys flooded with faked key-signatures". The keyserver keys.openpgp.org works differently again: it publishes a key's identity information only after its owner confirms the email address.

The practical effect: a key fetched from a keyserver now arrives without the third-party signatures the web of trust computes with, so users check fingerprints directly or obtain keys from their owners. The web of trust remains the model the syllabus sets and the rule GnuPG still computes over whatever signatures a user has.

The run: key IDs, a protected key, and a web of trust computed

The listing computes both fingerprints of RFC 9580's sample keys, shows why a short key ID is worth little, locks a private key under a passphrase, and applies GnuPG's rule to Asha's key ring.

munotes.in454

PGP Key Management and the Web of Trust

# PGP key management: fingerprints and key IDs, a protected private key, and the web of trust.
import base64, hashlib, hmac, math

# ---- 1. fingerprints and key IDs, against RFC 9580 Appendix A ----------------------------
v4_packet = bytes.fromhex('0453f35f0b16092b06010401da470f010107403f098994bdd916ed4053197934e4a8'
                          '7c80733a1280d62f8010992e43ee3b2406')         # A.1, the packet body
v4_fp = hashlib.sha1(b'\x99' + len(v4_packet).to_bytes(2, 'big') + v4_packet).hexdigest().upper()
armor = ('xioGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laPCsQYf'
         'GwoAAABCBYJjh3/jAwsJBwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxy')   # A.3, first lines
raw = base64.b64decode(armor)
v6_packet = raw[2:2 + raw[1]]
v6_fp = hashlib.sha256(b'\x9b' + len(v6_packet).to_bytes(4, 'big') + v6_packet).hexdigest().upper()
print('FINGERPRINTS AND KEY IDS')
print('  version 4: SHA-1 of 0x99, a 2-byte length and the key packet')
print('    fingerprint', ' '.join(v4_fp[i:i + 4] for i in range(0, 40, 4)))
print('    as RFC 9580 A.1 prints it:', v4_fp == 'C959BDBAFA32A2F89A153B678CFDE12197965A9A')
print('    key ID, the LOW 64 bits  :', v4_fp[-16:])
print('  version 6: SHA2-256 of 0x9B, a 4-byte length and the key packet')
print('    fingerprint', v6_fp[:32])
print('               ', v6_fp[32:])
print('    as RFC 9580 A.3 prints it:',
      v6_fp == 'CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9')
print('    key ID, the HIGH 64 bits :', v6_fp[:16])

# ---- 2. why a short key ID proves nothing ------------------------------------------------
seen, created = {}, 0x53F35F0B
while True:                               # same key material, a new creation time each try
    packet = v4_packet[:1] + created.to_bytes(4, 'big') + v4_packet[5:]
    fp = hashlib.sha1(b'\x99' + len(packet).to_bytes(2, 'big') + packet).hexdigest()
    short = fp[-4:]                       # the last 16 bits, a toy "short key ID"
    if short in seen:
        break
    seen[short] = created
    created += 1
print()
print('A 16-BIT KEY ID COLLIDES AFTER %d KEYS' % (len(seen) + 1))
print('  two different keys, both with the short ID', short.upper())
for bits in (16, 32, 64):
    print('  %2d-bit IDs: a collision is expected after about %s keys'
          % (bits, format(round(math.sqrt(math.pi / 2 * 2 ** bits)), ',')))

# ---- 3. a private key under a passphrase (Iterated and Salted S2K, RFC 9580 s.3.7.1.3) ----
def s2k(passphrase, salt, coded):
    count = (16 + (coded & 15)) << ((coded >> 4) + 6)   # octets to feed the hash
    data = salt + passphrase
    stream = (data * (count // len(data) + 1))[:max(count, len(data))]
    return hashlib.sha256(stream).digest()[:16]

def lock(key, secret):
    stream = hashlib.sha256(key + b'stream').digest()
    body = bytes(a ^ b for a, b in zip(secret, stream))
    return body + hmac.new(key, body, hashlib.sha256).digest()[:8]

def unlock(key, blob):
    body, tag = blob[:-8], blob[-8:]
    if not hmac.compare_digest(tag, hmac.new(key, body, hashlib.sha256).digest()[:8]):
        return None
    return bytes(a ^ b for a, b in zip(body, hashlib.sha256(key + b'stream').digest()))

private = bytes.fromhex('1a8b1ff05ded48e18bf50166c664ab023ea70003d78d9e41f5758a91d850f8d2')
salt, coded = bytes.fromhex('a1b2c3d4e5f60718'), 0xE0
stored = lock(s2k(b'monsoon evenings in pune', salt, coded), private)
print()
print('A PRIVATE KEY STORED UNDER A PASSPHRASE')
print('  the coded count 0x%02X means %s bytes of salt and passphrase are hashed'
      % (coded, format((16 + (coded & 15)) << ((coded >> 4) + 6), ',')))
print('  on disk: %d bytes, and not the private key: %s'
      % (len(stored), stored[:32] != private))
print('  right passphrase unlocks it:', unlock(s2k(b'monsoon evenings in pune', salt, coded),
                                             stored) == private)
print('  wrong passphrase           :', unlock(s2k(b'monsoon evenings in delhi', salt, coded),
                                             stored))

# ---- 4. the web of trust, GnuPG's rule ---------------------------------------------------
TRUST = {'bharat': 'full', 'chitra': 'marginal', 'dev': 'marginal', 'esha': 'marginal',
         'farhan': 'none', 'gauri': 'marginal', 'hari': 'marginal', 'isha': 'full'}
SIGNED_BY = {'bharat': ['asha'], 'chitra': ['asha'], 'dev': ['asha'], 'esha': ['asha'],
             'farhan': ['asha'], 'gauri': ['bharat'],
             'hari': ['chitra', 'dev'],                    # two marginal introducers
             'isha': ['chitra', 'dev', 'esha'],            # three marginal introducers
             'jaya': ['farhan'],                           # an introducer Asha does not trust
             'kiran': ['isha'], 'leela': ['kiran'], 'mohan': ['leela'],
             'nina': ['mohan'], 'om': ['nina']}
TRUST.update({'kiran': 'full', 'leela': 'full', 'mohan': 'full', 'nina': 'full'})

def validity(marginals_needed=3, completes_needed=1, max_depth=5):
    depth = {'asha': 0}                   # Asha's own key: valid by definition
    changed = True
    while changed:
        changed = False
        for key, signers in SIGNED_BY.items():
            if key in depth:
                continue
            good = [s for s in signers if s in depth and depth[s] < max_depth]
            full = sum(1 for s in good if s == 'asha' or TRUST.get(s) == 'full')
            marginal = sum(1 for s in good if TRUST.get(s) == 'marginal')
            if full >= completes_needed or marginal >= marginals_needed:
                depth[key] = 1 + min(depth[s] for s in good)
                changed = True
    return depth

print()
print('ASHA\'S KEYRING: WHICH KEYS ARE VALID (one complete or three marginal, depth 5 or less)')
d3, d2 = validity(), validity(marginals_needed=2)
for key in SIGNED_BY:
    print('  %-7s signed by %-22s %-8s %s' % (
        key, ', '.join(SIGNED_BY[key]),
        'valid' if key in d3 else 'NOT', '(valid if 2 marginals were enough)'
        if key in d2 and key not in d3 else ''))
munotes.in455

PGP Key Management and the Web of Trust

FINGERPRINTS AND KEY IDS
  version 4: SHA-1 of 0x99, a 2-byte length and the key packet
    fingerprint C959 BDBA FA32 A2F8 9A15 3B67 8CFD E121 9796 5A9A
    as RFC 9580 A.1 prints it: True
    key ID, the LOW 64 bits  : 8CFDE12197965A9A
  version 6: SHA2-256 of 0x9B, a 4-byte length and the key packet
    fingerprint CB186C4F0609A697E4D52DFA6C722B0C
                1F1E27C18A56708F6525EC27BAD9ACC9
    as RFC 9580 A.3 prints it: True
    key ID, the HIGH 64 bits : CB186C4F0609A697

A 16-BIT KEY ID COLLIDES AFTER 180 KEYS
  two different keys, both with the short ID 78E8
  16-bit IDs: a collision is expected after about 321 keys
  32-bit IDs: a collision is expected after about 82,137 keys
  64-bit IDs: a collision is expected after about 5,382,943,231 keys

A PRIVATE KEY STORED UNDER A PASSPHRASE
  the coded count 0xE0 means 16,777,216 bytes of salt and passphrase are hashed
  on disk: 40 bytes, and not the private key: True
  right passphrase unlocks it: True
  wrong passphrase           : None

ASHA'S KEYRING: WHICH KEYS ARE VALID (one complete or three marginal, depth 5 or less)
  bharat  signed by asha                   valid
  chitra  signed by asha                   valid
  dev     signed by asha                   valid
  esha    signed by asha                   valid
  farhan  signed by asha                   valid
  gauri   signed by bharat                 valid
  hari    signed by chitra, dev            NOT      (valid if 2 marginals were enough)
  isha    signed by chitra, dev, esha      valid
  jaya    signed by farhan                 NOT
  kiran   signed by isha                   valid
  leela   signed by kiran                  valid
  mohan   signed by leela                  valid
  nina    signed by mohan                  NOT
  om      signed by nina                   NOT
munotes.in456

PGP Key Management and the Web of Trust

What the run establishes, in order.

The fingerprints are the RFC's. The version 4 fingerprint of the sample key is C959 BDBA ... 9796 5A9A, exactly as RFC 9580 A.1 prints it, and its key ID, the last 64 bits, is 8CFDE12197965A9A, the issuer key ID found in the signature packet of the previous chapter. The version 6 fingerprint matches A.3, and its key ID is the first 64 bits.

A short key ID proves nothing. Re-creating the same key at different moments changes its fingerprint, and after only 180 tries two different keys share a 16-bit ID. The birthday arithmetic gives the scale: about 82,137 keys before two share a 32-bit ID, and about 5.4 thousand million before two share a 64-bit one. Checking a key means checking its full fingerprint.

The private key on disk is not the private key. Under a key derived from the passphrase by hashing 16,777,216 bytes of salt and passphrase, it is stored as 40 unreadable bytes; the right passphrase unlocks it and a wrong one yields nothing.

GnuPG's rule, applied. Every key Asha signed is valid. Gauri's key is valid through a single signature by Bharat, whom Asha trusts fully. Hari's key, signed by two marginal introducers, is not valid; it would be if two were enough. Isha's key, signed by three marginal introducers, is valid. Jaya's key is signed only by Farhan, whose key is valid but whom Asha does not trust as an introducer, so it is not. Along the chain Kiran, Leela, Mohan, every key is valid up to Mohan, five steps from Asha; Nina, six steps away, is not, however fully everyone before her is trusted.

The web of trust against X.509

PGP web of trustX.509 hierarchy
Who vouches for a keyany user, by signing ita certification authority
Where trust startseach user's own key, and their own choicesthe roots in a trust store
How much to trust an introducerset by each user, privatelydecided by the CA's policy and the browser maker
Rule for accepting a keycomputed: one full or three marginal signatures, path of five or fewer (GnuPG)a valid certification path to a trusted root
Revocationa revocation certificate signed by the key itselfCRLs, OCSP, short lifetimes
Strengthno single authority to trust or to compromiseuniform, automatic, scales to billions
Weaknessdepends on users' care; hard to scaleeverything rests on the CAs
munotes.in457

PGP Key Management and the Web of Trust

What beginners get wrong here

Treating owner trust and key validity as one thing. Owner trust is about the person's judgement as an introducer and is set by you; validity is about whether the key is theirs and is computed.

Believing trust is transitive. Bharat's valid key does not make the keys he signs valid, unless you also trust Bharat as an introducer.

Checking a key by its key ID. Key IDs collide. Fingerprints are what are compared.

Thinking the private key is stored as it is. It is stored encrypted under a key derived from the passphrase, which is why a stolen key file still needs the passphrase.

Saying two marginal signatures make a key valid. GnuPG's default is three.

Quick revision

  • Keys in PGP: session keys (one-time), public and private key pairs, passphrase-based keys.
  • Fingerprint: version 4 SHA-1 (160 bits), key ID the last 64 bits; version 6 SHA2-256 (256 bits), key ID the first 64 bits. Key IDs are not unique.
  • Private key ring: timestamp, key ID, public key, encrypted private key, user ID. Public key ring: adds owner trust, key legitimacy, signatures, signature trust.
  • S2K: Simple (must not be generated), Salted, Iterated and Salted, Argon2 (recommended).
  • Owner trust (unknown, none, marginal, full; ultimate for your own) is assigned; validity is computed.
  • GnuPG rule: signed by you, or one full, or three marginal, with a path of five or fewer steps.
  • Revocation certificate: made when the key is made, kept safe.
  • Since 2019: GnuPG ignores key-signatures from keyservers; keys.openpgp.org verifies email addresses.

Test yourself

1. What is a key ID, why does PGP need one, and how is it obtained for version 4 and version 6 keys? A key ID is a 64-bit label identifying a public key. PGP needs it because a user may hold several key pairs, and each message must say which key was used without carrying the whole public key. For a version 4 key it is the last 64 bits of the SHA-1 fingerprint; for a version 6 key it is the first 64 bits of the SHA2-256 fingerprint.

2. List the fields of the private and public key rings. The private key ring holds, for each of the user's own key pairs, a timestamp, the key ID, the public key, the private key encrypted under a passphrase-derived key, and the user ID. The public key ring holds, for each other user's key, a timestamp, the key ID, the public key and user ID, together with the owner trust, the key legitimacy, the signatures on the key and the signature trust of each.

munotes.in458

PGP Key Management and the Web of Trust

3. Distinguish owner trust from key legitimacy. Owner trust is the user's own, private judgement of how carefully a key's owner checks identities before signing other keys; it is assigned by the user. Key legitimacy, or validity, is how confident the user can be that the key really belongs to the person named on it; it is computed by PGP from the signatures on the key and the owner trust of those who made them.

4. State GnuPG's rule for deciding that a key is valid. With its defaults, a key is valid if it has been signed by the user personally, or by one fully trusted key, or by three marginally trusted keys, all of them valid keys, and the path of signatures from it back to the user's own key is five steps or fewer. The numbers can be changed with the options --completes-needed, --marginals-needed and --max-cert-depth.

5. Asha trusts Chitra and Dev marginally and has signed both their keys. Hari's key is signed by Chitra and Dev only. Is it valid to Asha under GnuPG's defaults? No. Two marginally trusted signatures are fewer than the three required, and there is no signature by Asha or by a fully trusted key. It would become valid if a third marginally trusted introducer signed it, or if Asha lowered the required number of marginal signatures to two.

6. How is a PGP private key protected on disk, and what does RFC 9580 recommend? It is encrypted under a symmetric key derived from the user's passphrase by a string-to-key function, so that a stolen key file is useless without the passphrase. RFC 9580 forbids generating the simple, unsalted method and recommends Argon2, which consumes memory as well as time and so makes offline guessing expensive.

7. Compare the PGP web of trust with the X.509 hierarchy. In the web of trust any user may vouch for a key by signing it, each user decides privately whom to trust as an introducer, and validity is computed from those choices; there is no authority to trust or to compromise, but it depends on users' care and scales poorly. In X.509, certification authorities vouch for keys and relying parties accept a key if it has a valid path to a root in their trust store; it is uniform and automatic and scales to the whole web, but everything rests on the authorities.

Contents This chapter on its own page

munotes.in459

Chapter Seventy

S/MIME

Syllabus topic Module 2, "Electronic Mail Security: S/MIME"

In one line

S/MIME is PGP's job done the X.509 way: it signs and encrypts email using certificates issued by certification authorities, and it is built into the mail programs most organisations already use.

In the words an answer should use, which are RFC 8551's: S/MIME (Secure/Multipurpose Internet Mail Extensions) "provides a consistent way to send and receive secure MIME data. Digital signatures provide authentication, message integrity, and non-repudiation with proof of origin. Encryption provides data confidentiality. Compression can be used to reduce data size." The current version is 4.0, specified in RFC 8551 (April 2019).

MIME first

S/MIME is a security layer on top of MIME, and cannot be understood without it.

The problem MIME solved. Internet mail was defined in 1982 by RFC 822, for text. RFC 2045, which defines MIME, lists what that left out: non-text messages "are simply not mentioned"; text in character sets richer than US-ASCII, meaning most of the world's languages, had no mechanism; and mail systems limited messages to "relatively short lines (e.g. 1000 characters or less) of 7bit US-ASCII". A photograph, a spreadsheet, or a sentence with a rupee sign in it could not be sent as it was.

MIME's five header fields, from RFC 2045:

Header fieldWhat it says
MIME-Versionthat the message follows MIME; its value is 1.0
Content-Typewhat the content is, as a type and subtype, with parameters: text/plain; charset="utf-8"
Content-Transfer-Encodinghow the content was converted to survive mail transport
Content-IDan identifier, so that one part can refer to another
Content-Descriptiona plain-language description of the part

The seven top-level content types, from RFC 2046. Five are discrete, holding one kind of data, and two are composite, holding other parts:

TypeCommon subtypesHolds
textplain, enrichedreadable text in a stated character set
imagejpeg, gifa picture
audiobasicsound
videompegmoving pictures
applicationoctet-stream, pdfdata for a program, or raw bytes
multipartmixed, alternative, parallel, digestseveral parts, each with its own headers
messagerfc822, partial, external-bodyan encapsulated message, or a fragment of one

The transfer encodings, which say how bytes were made safe for mail:

EncodingMeaningUsed for
7bitshort lines of US-ASCII, unchangedplain English text
8bitshort lines that may contain 8-bit characterstext, where the mail path allows it
binaryany bytes, any line lengthonly where the path allows it
quoted-printableprintable ASCII left as it is; every other byte written as = and two hexadecimal digitstext that is mostly ASCII
base64three bytes to four printable characters, exactly radix-64binary data

Canonical form. Different computers end lines differently. Before a MIME entity is signed, it is put into a canonical form in which, for text, every line ends with a carriage return and a line feed. Both the signer and the verifier hash that form, so that a message is not rejected merely because a mail system changed its line endings. The run below shows what happens if that step is skipped.

munotes.in460

S/MIME

What S/MIME adds

S/MIME wraps MIME entities in the Cryptographic Message Syntax (CMS) of RFC 5652, the structure the run takes apart, and marks the result with its own MIME types.

The forms of an S/MIME message, from RFC 8551 s.3:

FormWhat it isWho can read the content
Enveloped datathe content encrypted with a one-time content-encryption key, and that key encrypted for each recipientonly the recipients
Signed datathe content and its signature packed together in one CMS object, then base64-encodedonly an S/MIME-capable reader
Clear-signed datathe content left readable as the first part of a multipart/signed message, the signature attached as a second partanyone; only S/MIME readers can check the signature
Signed and envelopedone form applied inside the other: signed then encrypted, or encrypted then signedonly the recipients
Compressed datathe content compressed, usually before the other operationsan S/MIME-capable reader

Clear-signing is why a signed business email opens normally in any mail program. The run below takes apart exactly such a message.

The MIME types. An S/MIME object travels as application/pkcs7-mime, with an smime-type parameter telling the mail program what is inside without opening it: enveloped-data, signed-data, certs-only, compressed-data or authEnveloped-data. A clear-signed message is multipart/signed with the protocol application/pkcs7-signature, and its signature part is named smime.p7s.

How a signed message is made, following RFC 8551:

  1. Prepare the MIME entity to be signed, and convert it to canonical form.
  2. Compute its digest, SHA-256.
  3. Build the signed attributes: the content type, the signing time, the digest just computed, and the sender's cryptographic capabilities.
  4. Sign the signed attributes with the sender's private key.
  5. Pack the digest algorithm, the sender's certificate and the signature into a CMS SignedData object, and attach it.

Note step 4: what is signed is not the message itself but the attributes, one of which is the message's digest. That lets the signing time and the capabilities be protected by the same signature.

Algorithms, RFC 8551

RFC 8551 uses two extra labels. MUST- means required now but expected to be downgraded; SHOULD+ means recommended now and expected to become required.

PurposeRequiredBeing phased in or out
Message digestSHA-256 and SHA-512
Signatures, receivingECDSA on P-256 with SHA-256; EdDSA on curve25519RSA PKCS #1 v1.5 with SHA-256 is MUST-; RSA-PSS with SHA-256 SHOULD
Signatures, sendingat least one of ECDSA P-256 or EdDSARSA PKCS #1 v1.5 MUST-
Protecting the content keyECDH on P-256; ECDH on X25519RSA encryption MUST-; RSA-OAEP SHOULD+
Content encryptionAES-128 GCM and AES-256 GCMAES-128 CBC MUST-; ChaCha20-Poly1305 SHOULD+
munotes.in461

S/MIME

The direction of travel is the same as in RFC 9580 for OpenPGP: away from RSA and towards elliptic curves, and towards authenticated encryption modes such as GCM.

Certificates in S/MIME

S/MIME uses X.509 version 3 certificates, exactly those of the X.509 chapters, validated by the same path rules. The user's email address appears in the certificate, in the subject alternative name as an rfc822Name, so that the mail program can check that the certificate belongs to the address the message came from.

The mail program, the user agent, has three jobs with certificates: generate or obtain the user's own key pair and certificate, from a CA; store and look up the certificates of correspondents, which can be taken from the signed messages they send, since a signed S/MIME message can carry the signer's certificate, as the one below does; and validate every certificate it relies on, including its path and its revocation status. An organisation's staff certificates come from a CA that it runs itself or that it buys certificates from.

The enhanced security services

RFC 2634 (June 1999) defines further services on top of S/MIME, built on triple wrapping: a message signed, then encrypted, then signed again.

  • Signed receipts. The sender asks for a receipt; the recipient's software returns a signed receipt covering the original message's signature, proving the message arrived and was checked.
  • Security labels. A signed attribute classifying the content, such as "confidential", which receiving systems can use for access control.
  • Secure mailing lists. A mail list agent receives one encrypted message and re-encrypts it for each member, so the sender need not know the list's membership.
  • Signing certificate. A signed attribute naming exactly which certificate was used to sign, so that one certificate cannot be substituted for another.

The run: a real signed message, taken apart

The message below was produced with OpenSSL 3.6.1, signing a short notice with a throwaway certificate made for the fictional address asha@college.example. It is exactly what a mail program would send, shown with plain line endings.

MIME-Version: 1.0
Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg="sha-256"; boundary="----14EF7E8710C9940FB86E19CC54042408"

This is an S/MIME signed message

------14EF7E8710C9940FB86E19CC54042408
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable

The examination fee for Semester V is =E2=82=B9 1,500, payable by 15 Octob=
er.

Asha Kulkarni, Examination Section

------14EF7E8710C9940FB86E19CC54042408
Content-Type: application/pkcs7-signature; name="smime.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"

MIIGTAYJKoZIhvcNAQcCoIIGPTCCBjkCAQExDTALBglghkgBZQMEAgEwCwYJKoZI
hvcNAQcBoIIDqzCCA6cwggKPoAMCAQICFGfKD1Yn+tJ141rJ+cRvUaeb8JVwMA0G
CSqGSIb3DQEBCwUAMD8xCzAJBgNVBAYTAklOMRgwFgYDVQQKDA9FeGFtcGxlIENv
bGxlZ2UxFjAUBgNVBAMMDUFzaGEgS3Vsa2FybmkwHhcNMjYwOTMwMTAxOTMwWhcN
MzYwOTI3MTAxOTMwWjA/MQswCQYDVQQGEwJJTjEYMBYGA1UECgwPRXhhbXBsZSBD
b2xsZWdlMRYwFAYDVQQDDA1Bc2hhIEt1bGthcm5pMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA5mV5Zuz+iPv/f1zKBE2CUAO8gED4Ax5keAqI2fMyB9dc
RfzfhT68B+K6OvwRqlxQeE6IyksOApBSVD1nrhH5iGqjg5H0aHlWgnjKwNjrumLf
jcZ/NyTEfwL0p1KfDzH5cK/USWCSKF5CIlKpwbp5duMPfkGTFJt776iRN1XfwGwp
sXDuJ/WwBnLv7epR1WHQMMVno2hbtl3TNDNOgSnVxiNIa14O+ik9mtEEFTVXbmGj
3v9lVWm91/w43O9p1GvzYmP3dHpWhpxm0ade0kG3aWGt8F8Wh4CQFl42iA2Rm4h0
tK9yTgW4A5WzPgDd1OHpPo/07StEYxQSEh31SRZrVQIDAQABo4GaMIGXMB0GA1Ud
DgQWBBTmruPRK4v0ukC+BQgCKdLC3t+IFjAfBgNVHSMEGDAWgBTmruPRK4v0ukC+
BQgCKdLC3t+IFjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdEQQYMBaBFGFzaGFAY29s
bGVnZS5leGFtcGxlMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcD
BDANBgkqhkiG9w0BAQsFAAOCAQEAZOBriMXb3OJ8U4l1UlTyfseb2TVxvhHb64yo
hH0bVmrpuS6WuBmnS6rIPVrnRhHbNn8lGiRR17SyuTbCta9XhiQLc5TNwQ2PS8iC
dqFYH85hHW9qw/ZVecXGFDJu3xbmzmMDl0iV1j7RcHxX2U8CXQeegqjbfQD5Gp72
OqIkbNx7QkjygaH/tBUcCXyHRvPtZrmmAtAZW3bfQN1fVX63Htkx3uDsu1WQl5M9
5W+D8N/9Cf/lEbfmek4CnVk5jPD3S8x/i/2e7k5Zgh3+cYHHptst+pnduUcHsz7h
oMfwPWd+od5ongMOUAY5q21RrVElZVuAfBXCKo+jI68LyIU/YjGCAmcwggJjAgEB
MFcwPzELMAkGA1UEBhMCSU4xGDAWBgNVBAoMD0V4YW1wbGUgQ29sbGVnZTEWMBQG
A1UEAwwNQXNoYSBLdWxrYXJuaQIUZ8oPVif60nXjWsn5xG9Rp5vwlXAwCwYJYIZI
AWUDBAIBoIHkMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkF
MQ8XDTI2MDkzMDEwMTkzMFowLwYJKoZIhvcNAQkEMSIEIPZgHkv4i0xH6s63sQ5o
4ADFyZZyCS2hmbpT73ELtdOCMHkGCSqGSIb3DQEJDzFsMGowCwYJYIZIAWUDBAEq
MAsGCWCGSAFlAwQBFjALBglghkgBZQMEAQIwCgYIKoZIhvcNAwcwDgYIKoZIhvcN
AwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsOAwIHMA0GCCqGSIb3DQMCAgEoMA0G
CSqGSIb3DQEBAQUABIIBAMPGYtvfDqHzA8g0sRqk1v2pSpHwcT/Z0g6E0O0TuBQn
CRRPhd1ePnLGcvei9k16fEkTv+RLdQtlpVY/TIu6lxVPD71E1r9PqAmiwkMXek/h
s6V5Gkn54tBoJIRlFHfzaTru7/xH3dumTWhLvlodmo2gBWGsh6mhKATNrs8jAryA
s57SFQHozS27xfTVr5ROKz/TnselobtsXV8hYk5zPiJezc6I/YXk3GxcnUI0zby3
zyjrzWQ+TEir0LAz6lOd+e/oRHq/OrCo8zXV1GItTir74lYFYgFIzlh2rNTmpzxF
8OMKaYXICTggjfsGSjMFBIjIAfQukysSuex1mk1+Oi0=

------14EF7E8710C9940FB86E19CC54042408--
munotes.in462

S/MIME

The listing reads the MIME structure, decodes the quoted-printable text, takes the CMS signature apart with the same DER reader as the X.509 chapters, and verifies the signature from integers alone.

# A real S/MIME signed message, taken apart and verified: MIME, then CMS, then RSA.
import base64, datetime, hashlib, quopri

raw = open('signed-message.eml').read()
header, rest = raw.split('\n\n', 1)
print('THE OUTER MIME HEADERS')
for line in header.split('\n'):
    name, value = line.split(': ', 1)
    params = value.split('; ')
    print('  %s: %s' % (name, params[0]))
    for p in params[1:]:
        print('      ; %s' % p)
boundary = header.split('boundary="')[1].split('"')[0]
parts = rest.split('--' + boundary)
signed_part, signature_part = parts[1][1:-1], parts[2]

part_head, part_body = signed_part.split('\n\n', 1)
print()
print('PART 1, THE SIGNED CONTENT')
for line in part_head.split('\n'):
    print(' ', line)
print('  quoted-printable, as sent :', part_body.split('\n')[0])
print('  decoded                   :',
      quopri.decodestring(part_body.encode()).decode().split('\n')[0])

# ---- a DER reader, as in the X.509 chapters ----------------------------------------------
def tlv(d, i):
    tag, n, j = d[i], d[i + 1], i + 2
    if n & 0x80:
        k = n & 0x7F
        n, j = int.from_bytes(d[j:j + k], 'big'), j + k
    return tag, j, j + n

def items(d, s, e):
    out = []
    while s < e:
        t, a, b = tlv(d, s)
        out.append((t, a, b, s)); s = b
    return out

def oid(b):
    arcs, v = [b[0] // 40, b[0] % 40], 0
    for x in b[1:]:
        v = (v << 7) | (x & 0x7F)
        if not x & 0x80: arcs.append(v); v = 0
    return '.'.join(map(str, arcs))

NAMES = {'1.2.840.113549.1.7.2': 'signedData', '1.2.840.113549.1.7.1': 'data',
         '1.2.840.113549.1.9.3': 'contentType', '1.2.840.113549.1.9.5': 'signingTime',
         '1.2.840.113549.1.9.4': 'messageDigest', '1.2.840.113549.1.9.15': 'smimeCapabilities',
         '2.16.840.1.101.3.4.2.1': 'SHA-256', '2.16.840.1.101.3.4.1.42': 'AES-256-CBC',
         '2.16.840.1.101.3.4.1.22': 'AES-192-CBC', '2.16.840.1.101.3.4.1.2': 'AES-128-CBC',
         '1.2.840.113549.3.7': 'Triple-DES-CBC', '1.2.840.113549.3.2': 'RC2-CBC',
         '1.3.14.3.2.7': 'DES-CBC', '1.2.840.113549.1.1.1': 'rsaEncryption'}

der = base64.b64decode(signature_part.split('\n\n', 1)[1])
_, s, e = tlv(der, 0)
content_type, wrapper = items(der, s, e)
signed_data = items(der, *tlv(der, wrapper[1])[1:])
certificates = next(x for x in signed_data if x[0] == 0xA0)
signer = items(der, signed_data[-1][1], signed_data[-1][2])[0]
fields = items(der, signer[1], signer[2])
signed_attrs = next(x for x in fields if x[0] == 0xA0)
signature = next(x for x in reversed(fields) if x[0] == 0x04)

print()
print('PART 2, THE SIGNATURE: %d bytes of CMS (RFC 5652)' % len(der))
print('  content type            ', NAMES[oid(der[content_type[1]:content_type[2]])])
attrs = {}
for _, a, b, _ in items(der, signed_attrs[1], signed_attrs[2]):
    p = items(der, a, b)
    attrs[NAMES[oid(der[p[0][1]:p[0][2]])]] = items(der, p[1][1], p[1][2])[0]
t = attrs['signingTime']
print('  signed attribute signingTime   ',
      datetime.datetime.strptime(der[t[1]:t[2]].decode(), '%y%m%d%H%M%SZ'), 'UTC')
digest = der[attrs['messageDigest'][1]:attrs['messageDigest'][2]]
print('  signed attribute messageDigest ', digest.hex()[:32] + '...')
caps = attrs['smimeCapabilities']
offered = []
for cap in items(der, caps[1], caps[2]):
    alg = items(der, cap[1], cap[2])
    label = NAMES.get(oid(der[alg[0][1]:alg[0][2]]), oid(der[alg[0][1]:alg[0][2]]))
    if len(alg) > 1 and alg[1][0] == 0x02:              # RC2 carries its key size
        label += ' %d-bit' % int.from_bytes(der[alg[1][1]:alg[1][2]], 'big')
    offered.append(label)
print('  signed attribute smimeCapabilities, in the sender\'s order of preference:')
for i in range(0, len(offered), 4):
    print('      ' + ', '.join(offered[i:i + 4]))

cert = items(der, certificates[1], certificates[2])[0]
c = der[cert[3]:cert[2]]
tbs = items(c, *tlv(c, 0)[1:])[0]
f = items(c, tbs[1], tbs[2])
cn = [c[p[1][1]:p[1][2]].decode() for _, s1, e1, _ in items(c, f[5][1], f[5][2])
      for _, s2, e2, _ in items(c, s1, e1) for p in [items(c, s2, e2)]
      if oid(c[p[0][1]:p[0][2]]) == '2.5.4.3'][0]
spki = items(c, f[6][1], f[6][2])
key = c[spki[1][1] + 1:spki[1][2]]
(_, a, b, _), (_, x, y, _) = items(key, *tlv(key, 0)[1:])
n, e = int.from_bytes(key[a:b], 'big'), int.from_bytes(key[x:y], 'big')
print('  certificate carried inside    ', cn, '| RSA-%d' % n.bit_length())

# ---- verification, RFC 8551 s.3.1.1 and RFC 5652 s.5.4 -----------------------------------
def rsa_ok(message, sig):
    size = (n.bit_length() + 7) // 8
    tail = bytes.fromhex('3031300d060960864801650304020105000420') + \
        hashlib.sha256(message).digest()
    return pow(sig, e, n).to_bytes(size, 'big') == \
        b'\x00\x01' + b'\xff' * (size - 3 - len(tail)) + b'\x00' + tail

canonical = signed_part.replace('\n', '\r\n').encode()     # every line ends CR LF
attrs_as_set = b'\x31' + der[signed_attrs[3] + 1:signed_attrs[2]]
sig = int.from_bytes(der[signature[1]:signature[2]], 'big')
print()
print('VERIFYING')
print('  SHA-256 of the part in canonical form equals messageDigest:',
      hashlib.sha256(canonical).digest() == digest)
print('  the same part hashed WITHOUT converting line endings      :',
      hashlib.sha256(signed_part.encode()).digest() == digest)
print('  RSA signature over the signed attributes verifies          :', rsa_ok(attrs_as_set, sig))
altered = canonical.replace(b'1,500', b'15,000')
assert altered != canonical
print('  the fee changed from 1,500 to 15,000, digest still matches:',
      hashlib.sha256(altered).digest() == digest)

print()
print('THE COST OF MAKING IT MAIL-SAFE')
print('  signature: %d bytes of DER became %d characters of base64'
      % (len(der), len(signature_part.split('\n\n', 1)[1].replace('\n', ''))))
print('  whole message: %d characters, to carry %d characters of text'
      % (len(raw), len(quopri.decodestring(part_body.encode()).decode())))
munotes.in463

S/MIME

THE OUTER MIME HEADERS
  MIME-Version: 1.0
  Content-Type: multipart/signed
      ; protocol="application/pkcs7-signature"
      ; micalg="sha-256"
      ; boundary="----14EF7E8710C9940FB86E19CC54042408"

PART 1, THE SIGNED CONTENT
  Content-Type: text/plain; charset="utf-8"
  Content-Transfer-Encoding: quoted-printable
  quoted-printable, as sent : The examination fee for Semester V is =E2=82=B9 1,500, payable by 15 Octob=
  decoded                   : The examination fee for Semester V is ₹ 1,500, payable by 15 October.

PART 2, THE SIGNATURE: 1616 bytes of CMS (RFC 5652)
  content type             signedData
  signed attribute signingTime    2026-09-30 10:19:30 UTC
  signed attribute messageDigest  f6601e4bf88b4c47eaceb7b10e68e000...
  signed attribute smimeCapabilities, in the sender's order of preference:
      AES-256-CBC, AES-192-CBC, AES-128-CBC, Triple-DES-CBC
      RC2-CBC 128-bit, RC2-CBC 64-bit, DES-CBC, RC2-CBC 40-bit
  certificate carried inside     Asha Kulkarni | RSA-2048

VERIFYING
  SHA-256 of the part in canonical form equals messageDigest: True
  the same part hashed WITHOUT converting line endings      : False
  RSA signature over the signed attributes verifies          : True
  the fee changed from 1,500 to 15,000, digest still matches: False

THE COST OF MAKING IT MAIL-SAFE
  signature: 1616 bytes of DER became 2156 characters of base64
  whole message: 2854 characters, to carry 106 characters of text
munotes.in464

S/MIME

What the run establishes, in order.

The outer headers say everything a mail program needs. multipart/signed, the signature's type application/pkcs7-signature, the digest algorithm sha-256, and the boundary string that separates the parts.

Part 1 is readable by anyone. Its text is quoted-printable: the rupee sign is written =E2=82=B9, its three UTF-8 bytes, and a line that would be too long ends in =, a soft line break. Decoded, it is the notice as the sender wrote it.

Part 2 is a CMS SignedData object, 1,616 bytes, carrying signed attributes (the signing time, 30 September 2026 at 10:19:30 UTC; the message digest; and the sender's capabilities, in order of preference) and the signer's own certificate.

The signature verifies, but only in canonical form. The SHA-256 of part 1, with every line ended by a carriage return and line feed, equals the signed message-digest attribute. Hashed as it is displayed, with plain line endings, it does not. That is why canonicalisation is a required step, not a detail. The RSA signature over the signed attributes then verifies with the certificate's public key.

Any change is detected. Change the fee from 1,500 to 15,000 and the digest no longer matches, so the signature fails.

Mail-safety has a price. The 1,616-byte signature travels as 2,156 base64 characters, and a message carrying 106 characters of text is 2,854 characters long.

One observation to carry away. The capabilities OpenSSL 3.6.1 advertises by default still include Triple DES, RC2 with 40-bit keys and single DES, none of which RFC 8551 lists. Those capabilities are only offers, listed in order of preference, and here the list begins with AES-256. But software keeps old algorithms on offer long after the standards drop them.

S/MIME against PGP

This is the comparison a question spanning the email chapters asks for.

PGP (OpenPGP)S/MIME
StandardRFC 9580 (2024)RFC 8551 (2019), with CMS, RFC 5652
Who vouches for a keyusers, through the web of trust; or direct fingerprint checkscertification authorities, through X.509
Certificate formatOpenPGP keys with signatures on themX.509 version 3
Message structureOpenPGP packetsMIME entities wrapped in CMS
Encoding for mailradix-64 armorMIME, with base64
Clear-signingcleartext signature frameworkmultipart/signed
Required signature algorithm todayEd25519ECDSA P-256 or EdDSA; RSA PKCS #1 v1.5 still MUST-
Required content encryptionAES-128 in OCB modeAES-128 GCM and AES-256 GCM

The sentence that decides it: PGP and S/MIME protect email with the same cryptography, a hash signed with the sender's private key and a one-time content key encrypted for the recipient; they differ in how trust in public keys is established, the web of trust against certification authorities, and in the message formats that follow from that.

munotes.in465

S/MIME

What beginners get wrong here

Saying S/MIME encrypts the message with the recipient's public key. As in PGP, the message is encrypted with a one-time content key, and only that key is encrypted for each recipient.

Forgetting canonicalisation. A signature over text is computed on the canonical form, with every line ending in a carriage return and a line feed; hash anything else and verification fails, as the run shows.

Confusing signed data with clear-signed data. Signed data packs content and signature together and needs S/MIME to read; clear-signed data leaves the content readable by anyone.

Thinking the message is signed directly. The signature covers the signed attributes, which include the message's digest.

Mixing up MIME and S/MIME. MIME is the format for carrying any content in mail; S/MIME adds signing and encryption to MIME entities.

Quick revision

  • MIME (RFC 2045, 2046): five headers, MIME-Version, Content-Type, Content-Transfer-Encoding, Content-ID, Content-Description; seven types, text, image, audio, video, application, multipart, message; encodings 7bit, 8bit, binary, quoted-printable, base64; canonical form with CRLF.
  • S/MIME 4.0, RFC 8551 (April 2019): authentication, integrity, non-repudiation (signatures); confidentiality (encryption); compression.
  • Forms: enveloped, signed, clear-signed (multipart/signed, readable by anyone), signed and enveloped, compressed. Types: application/pkcs7-mime with smime-type; application/pkcs7-signature.
  • Signing: canonicalise, digest, signed attributes (content type, signing time, digest, capabilities), sign the attributes, pack in CMS SignedData.
  • Algorithms: SHA-256 and SHA-512; ECDSA P-256 or EdDSA (RSA PKCS #1 v1.5 MUST-); ECDH P-256 and X25519; AES-128 and AES-256 GCM.
  • Certificates: X.509 v3, email address in the subject alternative name.
  • RFC 2634: triple wrapping, signed receipts, security labels, secure mailing lists, signing certificate.
  • PGP against S/MIME: same cryptography, different trust (web of trust against CAs) and different formats.

Test yourself

1. What limitations of RFC 822 mail did MIME remove? RFC 822 mail could carry only text in US-ASCII: it had no provision for non-text content such as images, audio or programs, no way to carry the character sets needed for most languages, and mail systems limited messages to relatively short lines of 7-bit ASCII. MIME added typed content, character sets, multipart messages and transfer encodings that make any data safe for mail.

2. Name MIME's five header fields. MIME-Version, Content-Type, Content-Transfer-Encoding, Content-ID and Content-Description.

3. Distinguish S/MIME's signed data from its clear-signed data. Signed data packs the content and its signature into one CMS object encoded in base64, so only an S/MIME-capable program can even read the content. Clear-signed data sends a multipart/signed message whose first part is the ordinary, readable content and whose second part is the detached signature, so anyone can read the message and only S/MIME-capable programs can verify it.

munotes.in466

S/MIME

4. Describe how an S/MIME signed message is produced. The MIME entity is put into canonical form, with every line ending in a carriage return and line feed, and its SHA-256 digest is computed. The signed attributes are formed: the content type, the signing time, that digest and the sender's capabilities. These attributes are signed with the sender's private key, and the digest algorithm, the signer's certificate and the signature are packed into a CMS SignedData structure, which is attached to or wrapped around the content.

5. Why must the content be canonicalised before it is signed? Because mail systems and computers represent line endings differently and may change them in transit. Signing and verifying a single agreed form, with every line ending in a carriage return and a line feed, means the signature does not fail merely because line endings changed; a verifier that hashed the content as displayed would compute a different digest, as the run demonstrates.

6. Compare S/MIME with PGP. Both use the same cryptographic approach: a signature over a hash of the message, made with the sender's private key, and a one-time symmetric key that encrypts the message and is itself encrypted for each recipient. They differ in how trust in public keys is established, PGP by a web of trust or direct fingerprint checks and S/MIME by X.509 certificates from certification authorities, and in their formats, OpenPGP packets with radix-64 armor against MIME entities wrapped in CMS. S/MIME's reliance on certification authorities suits organisations that already run or buy certificates; PGP needs no authority at all.

7. What are the enhanced security services of RFC 2634? Services built on triple wrapping, a message signed, encrypted and signed again: signed receipts, which prove a message was received and its signature checked; security labels, signed classifications used for access control; secure mailing lists, in which a list agent re-encrypts a message for each member; and the signing certificate attribute, which binds the signature to the exact certificate used.

Contents This chapter on its own page

munotes.in467

Chapter Seventy-One

IP Security: What It Is For

Syllabus topic Module 2, "IP Security: Overview"

In one line

IPsec secures traffic at the IP layer, underneath every application, so that every program on a machine, or every machine behind a gateway, is protected without any of them being changed or even knowing.

In the words an answer should use: IP Security (IPsec) is a framework of open standards, defined by the IETF in RFC 4301 and the documents it names, that provides security services at the IP layer for IPv4 and IPv6: access control, connectionless integrity, data origin authentication, rejection of replayed packets, confidentiality, and limited traffic flow confidentiality, for traffic between hosts, between security gateways, or between a host and a gateway.

Why put security at the IP layer

The email chapters secured one application: PGP and S/MIME protect a message, and only the mail program knows about them. The web chapters will secure one connection: TLS protects a single conversation, and each application must use it. Both are real protection, and both have to be built into every application that wants it.

IPsec works one layer down. It protects the IP packets themselves. Every protocol that travels in IP packets is protected by the same mechanism: TCP, UDP, routing protocols, network management, protocols written before anyone thought about security, and protocols not yet written. The application does not know IPsec is there, and the run below shows exactly that.

Where security sitsExampleProtectsWho has to change
Application layerPGP, S/MIMEone application's datathat application
Transport layerTLSone connectioneach application that opens a connection
Network layerIPsecall IP traffic between the protected pointsnothing above IP

What IPsec provides

RFC 4301, the IPsec architecture, lists the services in its design objectives:

ServiceWhat it means
Access controlonly traffic the policy allows crosses the protected boundary
Connectionless integrityeach packet, on its own, is checked for alteration
Data origin authenticationeach packet is shown to come from the party holding the key
Rejection of replayed packetsa recorded packet sent again is refused
Confidentialitythe contents are encrypted
Limited traffic flow confidentialityan observer learns less about who is talking to whom, and how much

The word "connectionless" matters: IP delivers packets one by one, with no connection, and IPsec checks each packet on its own. It does not promise that packets arrive in order or at all; that remains TCP's job.

The boundary, and the three paths

RFC 4301 describes IPsec as creating a boundary between protected and unprotected interfaces. Every packet that crosses it is dealt with by policy in one of three ways: protected by IPsec, allowed to bypass IPsec, or discarded.

IPsec protects three kinds of path, and a compliant implementation supports them as follows:

munotes.in468

IP Security: What It Is For

  1. Host to host: two computers protect the traffic between themselves, end to end.
  2. Gateway to gateway: two security gateways, routers or firewalls running IPsec, protect all traffic between two networks, and the hosts behind them need nothing at all.
  3. Host to gateway: a single computer outside connects securely to a gateway, and through it to the network behind.

What it is used for

Each use is one of the three paths put to work.

  • Joining branch offices over the Internet. A college with two campuses puts a security gateway at each; everything between the campuses travels encrypted across the public Internet as if on a private line. This is a virtual private network (VPN), gateway to gateway.
  • Remote access. A staff member at home connects to the campus gateway, host to gateway, and works as though inside the campus network.
  • Connecting with partner organisations. Two organisations join selected parts of their networks, with policy deciding exactly which traffic may pass.
  • Protecting the network's own traffic. RFC 4301 gives the example of a router using IPsec to protect its routing protocol and its management traffic, the traffic that, if forged, lets an attacker redirect everyone else's.

The benefits, and why each follows

Transparent to applications. IPsec sits below the transport layer, so nothing in an application changes. A college need not wait for every program to add security.

Transparent to users. No user action is needed: no keys to manage, no settings to choose. The protection happens whether or not the user knows it exists.

One point of control at a gateway. Implemented in the firewall or router at the edge of a network, IPsec protects all traffic crossing the edge, and policy is set in one place rather than on every machine.

Protection for protocols that have none. Many protocols carried over IP were designed with no security. IPsec gives them integrity and confidentiality without redesigning them.

Where IPsec runs

RFC 4301 s.3.3 describes three ways to implement it:

ImplementationWhereSuited to
Nativebuilt into the operating system's IP stackhosts and gateways, when the stack's source is available
Bump-in-the-stackinserted between the IP stack and the network driversolder systems whose IP stack cannot be changed
Bump-in-the-wirea separate device on the cable, often with its own IP addressmilitary and some commercial systems; serving a host or a gateway

The documents

IPsec is a suite of standards, not one document, and a question on the IPsec overview often asks for them.

DocumentWhat it specifies
RFC 4301 (December 2005)the architecture: security associations, the policy and SA databases, modes
RFC 4302the Authentication Header (AH): integrity and origin, no encryption
RFC 4303the Encapsulating Security Payload (ESP): encryption, and integrity
RFC 7296IKEv2, the protocol that sets up keys and security associations
RFC 8221 (October 2017)which algorithms ESP and AH must, should, or must not use
RFC 8247the same for IKEv2
RFC 6071a roadmap to the whole suite
munotes.in469

IP Security: What It Is For

Keeping the algorithms in separate documents is deliberate. RFC 4301 explains that the algorithm lists "will be periodically updated to keep pace with computational and cryptologic advances" without touching the protocols themselves.

Two things textbooks still get wrong

AH is optional. RFC 4301 requires implementations to support ESP and only permits them to support AH, "because experience has shown that there are very few contexts in which ESP cannot provide the requisite security services". ESP can provide integrity without encryption, which is what AH does. The AH chapter explains why ESP won.

IPsec is not mandatory in IPv6. Many notes say IPv6 requires IPsec. RFC 8504, the IPv6 Node Requirements of January 2019, says: "Previously, IPv6 mandated implementation of IPsec". RFC 6434 changed that to a recommendation, a SHOULD, and RFC 8504 keeps the SHOULD, noting that for some devices, such as constrained sensors, "the full IPsec Architecture is not justified".

The run: two applications that never learn IPsec is there

The listing builds real IPv4 headers, with the header checksum computed the way RFC 1071 describes and first checked against that RFC's worked example. Two applications run on a pair of hosts: a chat program sending a line over UDP, and a file transfer over TCP. The applications are run twice, once with IPsec off and once with an ESP security association in transport mode, and not one line of either application changes. Then an attacker on the path tries three things. The cipher is a stand-in; the integrity check is HMAC with SHA-256, truncated to 128 bits, as RFC 8221 requires of ESP.

# IPsec's whole point, run: two applications protected without a line of them changing.
import hashlib, hmac, random

rng = random.Random(710)

def ones_complement_sum(data):             # RFC 1071, the Internet checksum's core
    if len(data) % 2:
        data += b'\x00'
    total = sum(int.from_bytes(data[i:i + 2], 'big') for i in range(0, len(data), 2))
    while total >> 16:
        total = (total & 0xFFFF) + (total >> 16)
    return total

print('RFC 1071 s.3 example, sum of 0001 f203 f4f5 f6f7:',
      '%04x' % ones_complement_sum(bytes.fromhex('0001f203f4f5f6f7')))

def ipv4(src, dst, protocol, payload):     # a real RFC 791 header, checksum and all
    header = bytearray(20)
    header[0], header[8], header[9] = 0x45, 64, protocol
    header[2:4] = (20 + len(payload)).to_bytes(2, 'big')
    header[12:16] = bytes(map(int, src.split('.')))
    header[16:20] = bytes(map(int, dst.split('.')))
    header[10:12] = (~ones_complement_sum(bytes(header)) & 0xFFFF).to_bytes(2, 'big')
    return bytes(header) + payload

UDP, TCP, ESP = 17, 6, 50                  # IANA protocol numbers

# ---- an ESP security association, transport mode (stand-in cipher; HMAC-SHA-256-128) ---
class SA:
    def __init__(self, spi, enc_key, auth_key):
        self.spi, self.enc, self.auth = spi, enc_key, auth_key
        self.sent, self.highest = 0, 0

    def stream(self, iv, n):
        return b''.join(hashlib.sha256(self.enc + iv + i.to_bytes(4, 'big')).digest()
                        for i in range(n // 32 + 1))[:n]

def esp_protect(sa, protocol, payload):
    sa.sent += 1
    head = sa.spi.to_bytes(4, 'big') + sa.sent.to_bytes(4, 'big')
    iv = rng.randbytes(16)
    pad = (-(len(payload) + 2)) % 4                   # RFC 4303 s.2.4: end on a 4-byte boundary
    plain = payload + bytes(range(1, pad + 1)) + bytes([pad, protocol])
    body = head + iv + bytes(a ^ b for a, b in zip(plain, sa.stream(iv, len(plain))))
    return body + hmac.new(sa.auth, body, hashlib.sha256).digest()[:16]

def esp_accept(sa, data):
    body, icv = data[:-16], data[-16:]
    if not hmac.compare_digest(icv, hmac.new(sa.auth, body, hashlib.sha256).digest()[:16]):
        return None, 'dropped: integrity check failed'
    seq = int.from_bytes(body[4:8], 'big')
    if seq <= sa.highest:
        return None, 'dropped: sequence number %d already seen' % seq
    sa.highest = seq
    iv, ct = body[8:24], body[24:]
    plain = bytes(a ^ b for a, b in zip(ct, sa.stream(iv, len(ct))))
    pad, protocol = plain[-2], plain[-1]
    return (protocol, plain[:-2 - pad]), 'accepted'

# ---- the network, a host stack, and two applications that know nothing of IPsec --------
wire = []

class Host:
    def __init__(self, addr, sa=None):
        self.addr, self.sa, self.inbox = addr, sa, []

    def send(self, dst, protocol, payload):          # the IP layer
        if self.sa:
            packet = ipv4(self.addr, dst.addr, ESP, esp_protect(self.sa, protocol, payload))
        else:
            packet = ipv4(self.addr, dst.addr, protocol, payload)
        wire.append(packet)
        return dst.receive(packet)

    def receive(self, packet):
        protocol, payload = packet[9], packet[20:]
        if protocol == ESP:
            result, verdict = esp_accept(self.sa, payload)
            if result is None:
                return verdict
            protocol, payload = result
        self.inbox.append((protocol, payload))
        return 'delivered'

def chat_app(host, peer, text):                      # "UDP": a line of chat
    return host.send(peer, UDP, text.encode())

def file_app(host, peer, name, content):              # "TCP": a file transfer
    return host.send(peer, TCP, name.encode() + b'\x00' + content)

def run(with_ipsec):
    global wire
    wire = []
    if with_ipsec:
        keys = rng.randbytes(16), rng.randbytes(16)
        a, b = Host('10.1.0.5', SA(0x1001, *keys)), Host('10.2.0.9', SA(0x1001, *keys))
    else:
        a, b = Host('10.1.0.5'), Host('10.2.0.9')
    chat_app(a, b, 'Practical exam moved to Thursday')
    file_app(a, b, 'marks.csv', b'roll,marks\n101,17\n102,19\n')
    return a, b

for label, on in (('WITHOUT IPsec', False), ('WITH IPsec (ESP, transport mode)', True)):
    a, b = run(on)
    print()
    print(label)
    for protocol, payload in b.inbox:
        print('  application received, protocol %2d: %r' % (protocol, payload[:40]))
    for packet in wire:
        print('  on the wire: protocol field %2d, %3d bytes, text visible: %s'
              % (packet[9], len(packet), b'Thursday' in packet or b'roll,marks' in packet))

print()
print('AN ATTACKER ON THE PATH, AGAINST THE PROTECTED HOSTS')
recorded = wire[0]
print('  replays a captured packet      :', b.receive(recorded))
forged = bytearray(recorded)
forged[-20] ^= 0x01                                   # flip one bit of the ciphertext
print('  alters one bit of a packet     :', b.receive(bytes(forged)))
fake = ipv4('10.1.0.5', '10.2.0.9', ESP, (0x1001).to_bytes(4, 'big') + (99).to_bytes(4, 'big')
            + rng.randbytes(60))
print('  forges a packet from 10.1.0.5  :', b.receive(fake))
print('  headers still valid IPv4 (checksum sums to ffff):',
      all(ones_complement_sum(p[:20]) == 0xFFFF for p in wire))
munotes.in470

IP Security: What It Is For

RFC 1071 s.3 example, sum of 0001 f203 f4f5 f6f7: ddf2

WITHOUT IPsec
  application received, protocol 17: b'Practical exam moved to Thursday'
  application received, protocol  6: b'marks.csv\x00roll,marks\n101,17\n102,19\n'
  on the wire: protocol field 17,  52 bytes, text visible: True
  on the wire: protocol field  6,  55 bytes, text visible: True

WITH IPsec (ESP, transport mode)
  application received, protocol 17: b'Practical exam moved to Thursday'
  application received, protocol  6: b'marks.csv\x00roll,marks\n101,17\n102,19\n'
  on the wire: protocol field 50,  96 bytes, text visible: False
  on the wire: protocol field 50, 100 bytes, text visible: False

AN ATTACKER ON THE PATH, AGAINST THE PROTECTED HOSTS
  replays a captured packet      : dropped: sequence number 1 already seen
  alters one bit of a packet     : dropped: integrity check failed
  forges a packet from 10.1.0.5  : dropped: integrity check failed
  headers still valid IPv4 (checksum sums to ffff): True
munotes.in471

IP Security: What It Is For

What the run establishes, in order.

The checksum routine is the Internet's. It reproduces the sum ddf2 that RFC 1071 works by hand.

The applications cannot tell the difference. With IPsec off and on, the receiving side delivers exactly the same chat line and exactly the same file to the applications. Their code is identical in both runs.

The wire can. Without IPsec the packets carry protocol numbers 17 and 6, UDP and TCP, and the text is readable to anyone on the path. With IPsec every packet carries protocol 50, ESP, is 44 or 45 bytes larger, and no text is visible.

The three attacks all fail. A captured packet sent again is refused because its sequence number has been seen. A packet with one bit changed fails the integrity check. A packet forged to look as if it came from the protected host fails the integrity check too, because the forger has no key. And every header remains valid IPv4, so routers along the way carry the packets exactly as before.

Distinctions that carry marks

IPsecTLSPGP and S/MIME
Layernetworktransportapplication
ProtectsIP packets, all traffic between two pointsone connectionone message
Applications must changenoyes, each oneyes, the mail program
Protects UDP, routing, management trafficyesnono
Typical useVPNs, site to site and remote accesswebsites, APIssigned and encrypted email
Host to hostGateway to gatewayHost to gateway
Protectsend to end, the two hoststwo networksone remote host to a network
Hosts behind the gatewaysneed IPsec themselvesneed nothingneed nothing
Exampletwo serverstwo campusesa teacher working from home
munotes.in472

IP Security: What It Is For

What beginners get wrong here

Saying IPsec only encrypts. Its services include access control, integrity, origin authentication and replay rejection; encryption is one of six.

Saying applications must be rewritten. The whole point of IPsec is that they need not be.

Saying IPsec is mandatory in IPv6. It was once; since RFC 6434, and in RFC 8504, it is a recommendation.

Saying IPsec replaces TLS. RFC 8504 says plainly that IPsec "is not viewed as the ideal security technology in all cases and is unlikely to displace the others".

Treating AH as required. ESP is required; AH is optional.

Quick revision

  • IPsec: security at the IP layer, for IPv4 and IPv6; transparent to applications and users.
  • Services (RFC 4301): access control, connectionless integrity, data origin authentication, rejection of replays, confidentiality, limited traffic flow confidentiality.
  • Boundary: every packet is protected, bypassed or discarded, by policy.
  • Paths: host to host, gateway to gateway (VPN), host to gateway (remote access).
  • Uses: branch offices, remote access, partners, protecting routing and management traffic.
  • Implementations: native, bump-in-the-stack, bump-in-the-wire.
  • Documents: RFC 4301 architecture, 4302 AH, 4303 ESP, 7296 IKEv2, 8221 and 8247 algorithms, 6071 roadmap.
  • Currency: ESP MUST, AH MAY; IPsec is a SHOULD, not a MUST, for IPv6 (RFC 8504, 2019).

Test yourself

1. What is IPsec, and what services does it provide? IPsec is the IETF's framework of standards for security at the IP layer, for IPv4 and IPv6, specified by RFC 4301 and its companion documents. It provides access control, connectionless integrity, data origin authentication, rejection of replayed packets, confidentiality, and limited traffic flow confidentiality.

2. Why is IPsec transparent to applications, and why does that matter? Because it operates below the transport layer, on the IP packets themselves, so applications hand data to TCP or UDP exactly as before and never see IPsec. It matters because every application on a machine, including old ones and ones with no security of their own, is protected at once, without being rewritten.

3. Describe three uses of IPsec. Joining the networks of two offices over the Internet through security gateways at each, a virtual private network; giving an individual remote access to an organisation's network from a host to its gateway; and protecting a router's own routing and management traffic, so that an attacker cannot forge routing updates. Connecting selected parts of partner organisations' networks is a fourth.

4. What are the three ways IPsec can be implemented? Natively, integrated into the operating system's IP stack; as a bump-in-the-stack, inserted between an existing IP stack and the network drivers, which suits systems whose stack cannot be changed; and as a bump-in-the-wire, a separate inline device, which may serve a single host or act as a gateway.

munotes.in473

IP Security: What It Is For

5. Is IPsec required in every IPv6 implementation? Not any more. IPv6 originally mandated IPsec, but RFC 6434 changed this to a recommendation, and RFC 8504, the current IPv6 Node Requirements of January 2019, keeps it as a SHOULD, recognising that for some constrained devices the full IPsec architecture is not justified. Where IPsec is implemented, ESP is required and AH is optional.

6. How does IPsec's position differ from that of TLS and of S/MIME? IPsec works at the network layer and protects all IP traffic between two points without any application changing. TLS works at the transport layer and protects one connection, and each application must use it. S/MIME works at the application layer and protects one email message inside the mail program.

Contents This chapter on its own page

munotes.in474

Chapter Seventy-Two

The IPsec Architecture: SA, SPD, Transport and Tunnel Mode

Syllabus topic Module 2, "IP Security: Overview, Architecture"

In one line

IPsec keeps two lists: a rulebook that decides, for every packet, whether to protect it, let it through or throw it away, and a key ring that holds, for every protected conversation, exactly how to protect it; and it wraps each protected packet in one of two ways, inside the packet or around the whole of it.

In the words an answer should use: the IPsec architecture (RFC 4301) rests on the security association (SA), a one-way relationship between a sender and a receiver that affords security services to the traffic it carries, identified at the receiver by its Security Parameters Index (SPI); on the Security Policy Database (SPD), an ordered list of policy entries whose selectors decide whether each packet is protected, bypassed or discarded; on the Security Association Database (SAD), which holds the parameters of every SA; and on two modes, transport mode, which protects the payload of an IP packet, and tunnel mode, which protects an entire IP packet inside a new one.

The security association

An SA is IPsec's unit of protection. RFC 4301 defines it as "a simplex 'connection' that affords security services to the traffic carried by it". Three consequences follow, and each is examined.

  1. An SA is one way. Two hosts talking both ways need two SAs, one in each direction. The key management protocol, IKE, creates them in pairs for this reason.
  2. An SA uses AH or ESP, not both. Traffic that needs both needs two SAs, applied one after the other; that is the subject of the chapter on combining SAs.
  3. An SA is found by its SPI. The Security Parameters Index is a 32-bit number chosen by the receiving end and carried in every AH or ESP header. When a protected packet arrives, the receiver uses the SPI, together if necessary with the destination address and the protocol, to look up which SA, and so which keys and algorithms, apply.

The textbook's identification. An SA is uniquely identified by three parameters: the SPI, the destination IP address, and the security protocol identifier, which says whether it is an AH or an ESP association. RFC 4301 adds that for ordinary unicast traffic the SPI alone suffices, and that a receiver may choose to use the protocol too.

What an SA holds: the SA parameters

RFC 4301 s.4.4.2.1 lists what the SAD must record for each SA.

ParameterWhat it is for
Security Parameters Indexidentifies the SA; written into outgoing headers, used to find the SA for incoming ones
Sequence number countergenerates the sequence number in each AH or ESP header; 64 bits by default
Sequence counter overflowwhether running out of sequence numbers stops the SA and is logged, or wraps round
Anti-replay windowa counter and a bit map recording which sequence numbers have arrived, to reject replays
AH informationthe integrity algorithm and its keys, if the SA is AH
ESP informationthe encryption algorithm, mode, keys and IV details; the integrity algorithm and keys; or a combined algorithm that does both
Lifetimehow long the SA lasts, in time or bytes or both; a soft limit that starts a replacement and a hard limit that ends the SA
IPsec protocol modetransport or tunnel
Path MTUthe largest packet the path will carry, so that packets can be sized after the IPsec headers are added
Tunnel endpointsin tunnel mode, the source and destination addresses for the new outer header
munotes.in475

The IPsec Architecture: SA, SPD, Transport and Tunnel Mode

RFC 4301 also lists a few flags for fragments and for the DSCP field, which carry no marks at this level.

The Security Policy Database

Policy comes first. Before any packet is protected, somebody has to decide which traffic needs protection, which may go out as it is, and which must never leave. That decision is the SPD, and RFC 4301 requires it to be consulted for all traffic crossing the IPsec boundary, protected or not.

Every entry ends in one of three actions:

  • PROTECT: apply IPsec, using the SA the entry names or causes to be created.
  • BYPASS: let the packet cross without IPsec.
  • DISCARD: drop it.

Every entry begins with selectors, the values it matches in a packet's headers. RFC 4301 s.4.4.1.1 requires these:

SelectorMatches
Remote IP address or addressesthe other end, as a single address, a list or a range
Local IP address or addressesthe addresses this implementation protects
Next layer protocolTCP, UDP, ICMP, or any
Local and remote portsfor TCP and UDP, lists or ranges of ports
ICMP type and code, mobility header typefor those protocols, the equivalent of ports
Namea user or system identity, for SAs set up by key management

The SPD is ordered. RFC 4301 says it plainly: the SPD "is an ordered database, consistent with the use of Access Control Lists (ACLs) or packet filters in firewalls". Entries overlap, so the first entry that matches decides. The run below shows what a wrong order does.

The SAD and the PAD

The SAD is the table of live SAs, one row per SA, with the parameters listed above. For outgoing traffic, the SPD entry that says PROTECT points to the SA; for incoming traffic, the SPI in the packet finds the SA directly.

RFC 4301 adds a third database, the Peer Authorisation Database (PAD), which records which peers may negotiate SAs and for which traffic. It links the key management protocol to the SPD, and is the reason a peer that authenticates successfully still cannot ask for protection of traffic it has no right to.

munotes.in476

The IPsec Architecture: SA, SPD, Transport and Tunnel Mode

How a packet is processed

Outbound, from the protected side:

  1. Match the packet against the SPD, in order, and take the first matching entry.
  2. If the action is DISCARD, drop it; if BYPASS, send it unprotected.
  3. If PROTECT, find the SA for this traffic in the SAD; if none exists yet, ask key management to create a pair.
  4. Apply AH or ESP in the SA's mode, add the next sequence number, and send.

Inbound, from the unprotected side:

  1. If the packet carries AH or ESP, use its SPI to find the SA; if there is none, discard it and log the event.
  2. Check the sequence number against the anti-replay window, verify the integrity check, decrypt.
  3. Check that the packet that comes out matches the SA's selectors. A key alone does not entitle a sender to deliver any traffic it likes through the SA.
  4. If the packet was not protected, look it up in the SPD: it is accepted only if policy says BYPASS.

Transport mode and tunnel mode

Three rows of boxes. The original packet is an IP header, a TCP header and data. In transport mode an IPsec header is inserted between the IP header and the TCP header. In tunnel mode a new IP header and an IPsec header come first, followed by the whole original packet: its IP header, TCP header and data.

Figure 72.1 Transport mode inserts the IPsec header into the packet; tunnel mode puts the whole packet inside a new one.

Transport mode protects what the IP packet carries. The IPsec header goes immediately after the original IP header and before the TCP or UDP header. The original addresses stay as they are, and the protection applies to the upper-layer protocol and its data. RFC 4301 describes it as "typically employed between a pair of hosts to provide end-to-end security services".

Tunnel mode protects the entire packet. The whole original packet, its header included, becomes the payload of a new IP packet with a new outer header. The outer header carries the addresses of the two IPsec endpoints, usually gateways; the inner header carries the real source and destination. RFC 4301: "whenever either end of a security association is a security gateway, the SA MUST be tunnel mode", apart from traffic addressed to the gateway itself.

What each protects, drawn from RFC 4301 s.4.1:

Transport modeTunnel mode
AHthe upper-layer data, plus the unchanging parts of the IP headerthe whole inner packet, plus the unchanging parts of the outer header
ESPthe upper-layer data, not the IP header before itthe whole inner packet, header included, not the outer header
Addresses an observer seesthe real source and destinationonly the two tunnel endpoints
Size addedthe IPsec header and trailerthose, plus a whole new IP header
Typical usehost to host, end to endgateway to gateway, host to gateway: VPNs
munotes.in477

The IPsec Architecture: SA, SPD, Transport and Tunnel Mode

Why tunnel mode for gateways. A gateway protects traffic for many hosts behind it. The packet must reach the gateway that holds the SA, not the host behind it, so the outer header has to name the gateway. And the inner header, hidden inside, keeps an observer on the Internet from learning which host behind the gateway is talking to which: the traffic flow confidentiality of the previous chapter's list.

The run: a gateway's databases at work

The listing sets up the gateway of campus A, protecting 10.1.0.0/16 and talking to campus B's gateway at 203.0.113.2, with an SPD of five ordered entries. It sends seven packets through it, prints the pair of SAs the first protected packet caused, runs the same traffic under a wrongly ordered SPD, handles four incoming packets, and prices one 60-byte packet in each mode using AES-GCM's real overheads.

# A security gateway's two databases at work: the SPD decides, the SAD protects.
import ipaddress

def net(text):
    return ipaddress.ip_network(text)

ANY = None

class Rule:                               # one SPD entry: selectors, then an action
    def __init__(self, name, local, remote, proto, port, action, tunnel_to=None):
        self.name, self.local, self.remote = name, local, remote
        self.proto, self.port, self.action, self.tunnel_to = proto, port, action, tunnel_to

    def matches(self, p, inbound=False):  # local and remote swap roles for inbound packets
        mine, theirs = (p['dst'], p['src']) if inbound else (p['src'], p['dst'])
        return ((self.local is ANY or ipaddress.ip_address(mine) in net(self.local)) and
                (self.remote is ANY or ipaddress.ip_address(theirs) in net(self.remote)) and
                (self.proto is ANY or p['proto'] == self.proto) and
                (self.port is ANY or p['dport'] == self.port))

SPD = [Rule('campus A to campus B', '10.1.0.0/16', '10.2.0.0/16', ANY, ANY,
            'PROTECT', tunnel_to='203.0.113.2'),
       Rule('DNS', ANY, ANY, 'udp', 53, 'BYPASS'),
       Rule('no Telnet anywhere', ANY, ANY, 'tcp', 23, 'DISCARD'),
       Rule('HTTPS, already under TLS', ANY, ANY, 'tcp', 443, 'BYPASS'),
       Rule('everything else', ANY, ANY, ANY, ANY, 'DISCARD')]

SAD = {}                                   # SPI -> security association

def make_pair(rule):                       # IKE makes SAs in pairs, one each way
    for spi, direction, ends in ((0x2A01, 'outbound', ('198.51.100.1', rule.tunnel_to)),
                                 (0x5B01, 'inbound', (rule.tunnel_to, '198.51.100.1'))):
        SAD[spi] = {'SPI': spi, 'direction': direction, 'seq': 0, 'mode': 'tunnel',
                    'tunnel': '%s to %s' % ends, 'ESP': 'AES-GCM-16, 256-bit key',
                    'window': 64, 'lifetime': 'soft 3,000 s, hard 3,600 s',
                    'selectors': rule}

def outbound(p, spd):                      # RFC 4301 s.5.1: first matching entry decides
    rule = next(r for r in spd if r.matches(p))
    if rule.action != 'PROTECT':
        return rule, rule.action, None
    sa = next((s for s in SAD.values() if s['selectors'] is rule
               and s['direction'] == 'outbound'), None)
    if sa is None:                         # no SA yet: key management would make them
        make_pair(rule)
        sa = SAD[0x2A01]
    sa['seq'] += 1
    return rule, 'PROTECT', sa

def show(p, spd=SPD):
    rule, action, sa = outbound(p, spd)
    tail = ' SPI 0x%X, sequence %d' % (sa['SPI'], sa['seq']) if sa else ''
    print('  %-9s -> %-10s %-3s %-4s | "%s": %s%s' % (
        p['src'], p['dst'], p['proto'], p['dport'], rule.name, action, tail))

print('OUTBOUND, THROUGH THE GATEWAY OF CAMPUS A')
traffic = [dict(src='10.1.4.7', dst='10.2.8.1', proto='tcp', dport=80),
           dict(src='10.1.4.7', dst='10.2.8.1', proto='udp', dport=514),
           dict(src='10.1.4.7', dst='192.0.2.53', proto='udp', dport=53),
           dict(src='10.1.4.7', dst='192.0.2.80', proto='tcp', dport=443),
           dict(src='10.1.4.7', dst='192.0.2.80', proto='tcp', dport=23),
           dict(src='10.1.4.7', dst='192.0.2.80', proto='tcp', dport=8080),
           dict(src='10.1.9.2', dst='10.2.0.40', proto='tcp', dport=22)]
for p in traffic:
    show(p)

print()
print('THE PAIR OF SECURITY ASSOCIATIONS THE FIRST PACKET CAUSED, AS THE SAD HOLDS THEM')
for sa in SAD.values():
    print('  SPI 0x%X, %s' % (sa['SPI'], sa['direction']))
    for field in ('seq', 'mode', 'tunnel', 'ESP', 'window', 'lifetime'):
        print('    %-9s %s' % (field, sa[field]))

print()
print('THE SAME TRAFFIC, WITH TWO RULES IN THE WRONG ORDER')
wrong = [SPD[0], SPD[1], Rule('any TCP to the Internet', ANY, ANY, 'tcp', ANY, 'BYPASS'),
         SPD[2], SPD[4]]
for p in traffic[4:6]:
    show(p, wrong)

# ---- inbound: the SPI finds the SA; then the inner packet must fit the SA's selectors --
def inbound(spi, inner):
    sa = SAD.get(spi)
    if sa is None or sa['direction'] != 'inbound':
        return 'discarded and logged: no inbound SA with SPI 0x%X' % spi
    if not sa['selectors'].matches(inner, inbound=True):
        return 'discarded: %s is not a source SA 0x%X carries' % (inner['src'], spi)
    return 'accepted on SA 0x%X, delivered to %s' % (spi, inner['dst'])

print()
print('INBOUND, FROM CAMPUS B\'S GATEWAY 203.0.113.2')
reply = dict(src='10.2.8.1', dst='10.1.4.7', proto='tcp', dport=80)
spoof = dict(src='10.9.9.9', dst='10.1.4.7', proto='tcp', dport=80)
print('  SPI 0x5B01, inner 10.2.8.1 -> 10.1.4.7 :', inbound(0x5B01, reply))
print('  SPI 0x2A01, the same inner packet      :', inbound(0x2A01, reply))
print('  SPI 0x7777, the same inner packet      :', inbound(0x7777, reply))
print('  SPI 0x5B01, inner 10.9.9.9 -> 10.1.4.7 :', inbound(0x5B01, spoof))

# ---- what each mode costs one 60-byte packet (20 IP + 20 TCP + 20 data) ------------------
def esp_bytes(inner):                      # SPI 4, sequence 4, IV 8, trailer 2 + pad, ICV 16
    pad = (-(inner + 2)) % 4
    return 4 + 4 + 8 + inner + pad + 2 + 16

print()
print('ONE 60-BYTE PACKET, AES-GCM (8-BYTE IV, 16-BYTE ICV)')
print('  transport mode: 20 IP + ESP around the 40 bytes after it = %d bytes'
      % (20 + esp_bytes(40)))
print('  tunnel mode   : 20 new IP + ESP around all 60 bytes      = %d bytes'
      % (20 + esp_bytes(60)))
munotes.in478

The IPsec Architecture: SA, SPD, Transport and Tunnel Mode

OUTBOUND, THROUGH THE GATEWAY OF CAMPUS A
  10.1.4.7  -> 10.2.8.1   tcp 80   | "campus A to campus B": PROTECT SPI 0x2A01, sequence 1
  10.1.4.7  -> 10.2.8.1   udp 514  | "campus A to campus B": PROTECT SPI 0x2A01, sequence 2
  10.1.4.7  -> 192.0.2.53 udp 53   | "DNS": BYPASS
  10.1.4.7  -> 192.0.2.80 tcp 443  | "HTTPS, already under TLS": BYPASS
  10.1.4.7  -> 192.0.2.80 tcp 23   | "no Telnet anywhere": DISCARD
  10.1.4.7  -> 192.0.2.80 tcp 8080 | "everything else": DISCARD
  10.1.9.2  -> 10.2.0.40  tcp 22   | "campus A to campus B": PROTECT SPI 0x2A01, sequence 3

THE PAIR OF SECURITY ASSOCIATIONS THE FIRST PACKET CAUSED, AS THE SAD HOLDS THEM
  SPI 0x2A01, outbound
    seq       3
    mode      tunnel
    tunnel    198.51.100.1 to 203.0.113.2
    ESP       AES-GCM-16, 256-bit key
    window    64
    lifetime  soft 3,000 s, hard 3,600 s
  SPI 0x5B01, inbound
    seq       0
    mode      tunnel
    tunnel    203.0.113.2 to 198.51.100.1
    ESP       AES-GCM-16, 256-bit key
    window    64
    lifetime  soft 3,000 s, hard 3,600 s

THE SAME TRAFFIC, WITH TWO RULES IN THE WRONG ORDER
  10.1.4.7  -> 192.0.2.80 tcp 23   | "any TCP to the Internet": BYPASS
  10.1.4.7  -> 192.0.2.80 tcp 8080 | "any TCP to the Internet": BYPASS

INBOUND, FROM CAMPUS B'S GATEWAY 203.0.113.2
  SPI 0x5B01, inner 10.2.8.1 -> 10.1.4.7 : accepted on SA 0x5B01, delivered to 10.1.4.7
  SPI 0x2A01, the same inner packet      : discarded and logged: no inbound SA with SPI 0x2A01
  SPI 0x7777, the same inner packet      : discarded and logged: no inbound SA with SPI 0x7777
  SPI 0x5B01, inner 10.9.9.9 -> 10.1.4.7 : discarded: 10.9.9.9 is not a source SA 0x5B01 carries

ONE 60-BYTE PACKET, AES-GCM (8-BYTE IV, 16-BYTE ICV)
  transport mode: 20 IP + ESP around the 40 bytes after it = 96 bytes
  tunnel mode   : 20 new IP + ESP around all 60 bytes      = 116 bytes
munotes.in479

The IPsec Architecture: SA, SPD, Transport and Tunnel Mode

What the run establishes, in order.

Every packet meets exactly one decision, the first entry that matches. Traffic from campus A to campus B, of any protocol, is PROTECTED on SPI 0x2A01, with sequence numbers 1, 2 and 3 as the packets go. DNS and HTTPS are BYPASSED, the second because TLS already protects it. Telnet is DISCARDED by name, and port 8080 by the final catch-all entry.

The first protected packet caused a pair of SAs. SPI 0x2A01 carries traffic out from 198.51.100.1 to 203.0.113.2; SPI 0x5B01 carries traffic in, the other way. Each records its mode, its tunnel endpoints, its algorithm, a 64-packet replay window and a soft and hard lifetime; the outbound one has sent three packets.

Order is policy. Put a broad "any TCP may leave" entry above the Telnet entry and both Telnet and port 8080 go out unprotected. No rule was deleted; one was moved.

Inbound, the SPI decides, and then the selectors. A reply from 10.2.8.1 on the inbound SA 0x5B01 is accepted. The same packet on 0x2A01 is refused, because that SA runs the other way. An unknown SPI is discarded and logged. And a packet that decrypts correctly on 0x5B01 but claims to come from 10.9.9.9 is discarded, because that SA carries traffic only from campus B's addresses.

munotes.in480

The IPsec Architecture: SA, SPD, Transport and Tunnel Mode

Tunnel mode costs a header more. The same 60-byte packet becomes 96 bytes in transport mode and 116 in tunnel mode: the difference is the new 20-byte outer header, the price of hiding the real addresses.

Distinctions that carry marks

SPDSAD
Holdspolicy: which traffic gets which treatmentparameters of each live SA
Consultedfor every packet crossing the boundaryfor protected packets
Found bymatching selectors, in orderthe SPD entry (outbound) or the SPI (inbound)
Analogya firewall's rule lista key ring
Security associationSPI
What it isa one-way relationship with its algorithms, keys and statea 32-bit number
Where it livesin the SAD of each endpointin every AH or ESP header, and in the SAD
Chosen bynegotiated by IKE, or set by handthe receiving end

What beginners get wrong here

Saying an SA is two-way. It is simplex. A two-way conversation needs two SAs, and the run's reply is refused on the outbound SPI.

Saying the SPI is chosen by the sender. The receiver chooses it, so that it can find the SA from it.

Thinking the SPD only lists protected traffic. It governs all traffic crossing the boundary, and BYPASS and DISCARD are as much its business as PROTECT.

Forgetting that SPD order matters. The first match decides, exactly as in a firewall.

Saying tunnel mode encrypts and transport mode authenticates. Mode says what is protected, and AH or ESP says how. Either protocol can be used in either mode.

Thinking the outer header in tunnel mode is protected by ESP. ESP protects the inner packet only; the outer header is covered by neither encryption nor ESP's integrity check.

Quick revision

  • SA (RFC 4301): simplex; AH or ESP; a pair for two-way traffic, made by IKE; identified by SPI, destination address and protocol; SPI chosen by the receiver.
  • SA parameters: SPI, sequence number counter, counter overflow, anti-replay window, AH information, ESP information, lifetime (soft and hard, time or bytes), mode, path MTU, tunnel endpoints.
  • SPD: ordered; selectors (remote and local addresses, next-layer protocol, ports, name); actions PROTECT, BYPASS, DISCARD; consulted for all traffic.
  • SAD: the live SAs; outbound found from the SPD, inbound found by SPI. PAD: which peers may negotiate what.
  • Transport mode: IPsec header after the IP header; protects the payload; host to host.
  • Tunnel mode: new outer header; protects the whole inner packet; required when either end is a gateway; hides the real addresses; costs a header more.

Test yourself

1. Define a security association and state how it is identified. A security association is a one-way relationship between a sender and a receiver that affords security services, by AH or by ESP but not both, to the traffic it carries. It is identified by the Security Parameters Index, a 32-bit value chosen by the receiver and carried in every AH or ESP header, together with the destination IP address and the security protocol identifier; for ordinary unicast traffic the SPI alone suffices.

munotes.in481

The IPsec Architecture: SA, SPD, Transport and Tunnel Mode

2. Why do two hosts that talk both ways need two SAs? Because an SA is simplex: it carries traffic in one direction only, with its own SPI, keys and sequence numbers. One SA protects traffic from the first host to the second and another protects traffic back, which is why IKE creates SAs in pairs.

3. List the parameters an SA holds. Its Security Parameters Index; a sequence number counter and a flag saying what happens when it overflows; an anti-replay window; the AH algorithm and keys, or the ESP encryption and integrity algorithms, modes and keys; its lifetime, as time, byte count or both, with soft and hard limits; its mode, transport or tunnel; the path MTU; and, in tunnel mode, the addresses of the tunnel endpoints.

4. What is the SPD, what are its three actions, and why is its order important? The Security Policy Database is the ordered list of policy entries consulted for every packet crossing the IPsec boundary. Each entry has selectors, such as addresses, protocol and ports, and an action: protect the packet with IPsec, bypass IPsec, or discard it. Because entries overlap, the first entry that matches decides, so a broad entry placed above a narrower one can silently override it, exactly as in a firewall's rule list.

5. Distinguish transport mode from tunnel mode. In transport mode the AH or ESP header is placed after the original IP header and protects the upper-layer data; the original addresses remain visible, and it is typically used end to end between hosts. In tunnel mode the entire original packet is carried inside a new IP packet whose header names the IPsec endpoints; the whole inner packet is protected and the real addresses are hidden, at the cost of an extra header, and it must be used whenever either end of the SA is a security gateway.

6. A packet arrives with a valid SPI and decrypts correctly, but its inner source address lies outside the SA's selectors. What happens, and why? It is discarded. After processing, the receiver checks that the packet matches the selectors of the SA it arrived on; possessing the key does not entitle a sender to inject traffic the SA was not set up to carry, so a peer cannot use an SA for a network it was never authorised for.

Contents This chapter on its own page

munotes.in482

Chapter Seventy-Three

The Authentication Header

Syllabus topic Module 2, "IP Security: Authentication Header"

In one line

The Authentication Header proves that an IP packet came from the holder of a shared key and was not changed on the way, including its source and destination addresses, but it hides nothing.

In the words an answer should use: the IP Authentication Header (AH), specified in RFC 4302, provides connectionless integrity and data origin authentication for IP datagrams, and optionally protection against replays, by carrying an Integrity Check Value computed with a keyed algorithm over the packet's payload and over those fields of the IP header that do not change in transit; it provides no confidentiality.

Why a header that only authenticates

Encryption is not always wanted or allowed. Network managers may need to read traffic; some settings do not permit encryption; and some traffic is public but must not be forged, such as a routing update. For these, what matters is who sent the packet and whether it was changed, and AH answers exactly that.

AH's distinctive promise is that it covers the IP header too. An attacker who rewrites the source address of an AH-protected packet breaks its integrity check. That is also why AH has fallen out of use, as the end of this chapter explains.

The header

AH sits after the IP header (in transport mode) or after a new outer IP header (in tunnel mode), and the IP header's protocol field is set to 51 to announce it. RFC 4302 s.2 defines six fields:

FieldSizeWhat it is
Next Header8 bitswhat comes after AH: 6 for TCP, 17 for UDP, 4 for an IPv4 packet in tunnel mode
Payload Length8 bitsthe length of AH in 32-bit words, minus 2
Reserved16 bitsset to zero, but still covered by the check
Security Parameters Index32 bitsidentifies the SA, and so the key and algorithm
Sequence Number32 bitsa counter that goes up by one with every packet on the SA, for anti-replay
Integrity Check Value (ICV)variable, a multiple of 32 bitsthe keyed hash over the packet

The Payload Length rule catches students. The three fixed words (next header to reserved, SPI, sequence number) plus the ICV, counted in 32-bit words, minus two. With a 128-bit ICV that is 3 + 4 - 2 = 5, and the header is (5 + 2) × 4 = 28 bytes. RFC 4302's own example is a 96-bit ICV, for which the field is 4.

What the ICV covers, and the mutable fields

The ICV is computed over the whole packet: the IP header, AH itself (with the ICV field set to zero), and everything after AH. But routers change some IP header fields on the way, and a check over those would fail on every packet.

munotes.in483

The Authentication Header

RFC 4302 sorts the IPv4 header's fields into three kinds:

KindIPv4 fieldsTreatment
Immutableversion, header length, total length, identification, protocol, source address, destination addresscovered as they are
Mutable but predictablethe destination address when source routing is usedcovered at the value it will have on arrival
MutableDSCP and ECN (the old type-of-service byte), flags, fragment offset, time to live, header checksumset to zero before the ICV is computed

Zeroed, not left out. RFC 4302 explains the choice: replacing each mutable field with zeros rather than omitting it keeps the alignment the same and means even the length of those fields cannot change unnoticed.

Why each mutable field changes. Routers decrement the time to live at every hop and so must recompute the header checksum; they may rewrite DSCP to give traffic a class of service and set ECN to signal congestion; and fragmentation along the path sets flags and fragment offset.

Anti-replay: the sequence number and the window

The sender starts each SA's counter at zero and puts the next value, beginning with 1, in every packet. It must never let the counter wrap round on an SA that uses anti-replay: before that happens, a new SA is set up.

The receiver keeps a window: a record of the highest sequence number accepted so far, N, and of which numbers in the range from N - W + 1 to N have arrived, where W is the window's width. RFC 4302 requires a window of at least 32 and prefers 64. For each arriving packet:

  1. If its number is left of the window, too old to judge, it is discarded.
  2. If its number is inside the window and has already been seen, it is a duplicate and is discarded.
  3. If it is inside the window and new, or right of the window, the ICV is checked.
  4. Only if the ICV verifies is the packet accepted and the window updated: a new highest number moves the window to the right.

Step 4 is the one that matters most. If the window moved before the ICV was checked, an attacker could send a forged packet with a huge sequence number and push the window past every genuine packet still on its way, which would then all be rejected as too old.

AH in the two modes

Two packet diagrams. In transport mode: the original IP header, then AH, then TCP, then data, with a bracket beneath the whole packet reading authenticated except the mutable fields of the IP header. In tunnel mode: a new IP header, AH, the original IP header, TCP and data, with a bracket beneath the whole packet reading authenticated except the mutable fields of the new IP header. A note says nothing is encrypted.

Figure 73.1 AH authenticates the entire packet it sits in, apart from the header fields routers are allowed to change.

Transport mode. AH follows the original IP header. The ICV covers the upper-layer data and the unchanging parts of the original header, source and destination addresses included.

munotes.in484

The Authentication Header

Tunnel mode. A new IP header is put in front, AH follows it, and the whole original packet comes after. The ICV covers the entire inner packet, its header in full, and the unchanging parts of the new outer header.

The run: AH on a real packet, and the window at work

The listing builds a real IPv4 packet carrying a TCP segment, adds AH in transport mode with HMAC-SHA2-256-128, the algorithm RFC 8221 requires, and prints each field. Then it plays the path: two things routers legitimately do, and four things an attacker or a NAT might do. Finally it runs a 32-packet anti-replay window over fourteen arrivals.

# The Authentication Header over a real IPv4 packet, and the anti-replay window worked through.
import hashlib, hmac

print('HMAC-SHA-256 reproduces RFC 4231 test case 2:',
      hmac.new(b'Jefe', b'what do ya want for nothing?', hashlib.sha256).hexdigest()
      .startswith('5bdcc146bf60754e'))

def checksum(header):                        # RFC 1071
    total = sum(int.from_bytes(header[i:i + 2], 'big') for i in range(0, len(header), 2))
    while total >> 16:
        total = (total & 0xFFFF) + (total >> 16)
    return ~total & 0xFFFF

def ipv4(src, dst, protocol, payload, ttl=64, tos=0):
    h = bytearray(20)
    h[0], h[1], h[8], h[9] = 0x45, tos, ttl, protocol
    h[2:4] = (20 + len(payload)).to_bytes(2, 'big')
    h[4:6] = (0x1C46).to_bytes(2, 'big')                  # identification
    h[6:8] = (0x4000).to_bytes(2, 'big')                  # flags: don't fragment
    h[12:16], h[16:20] = bytes(map(int, src.split('.'))), bytes(map(int, dst.split('.')))
    h[10:12] = checksum(bytes(h)).to_bytes(2, 'big')
    return bytearray(h) + payload

KEY, SPI, ICV_LEN = bytes(range(32)), 0x0000A001, 16    # HMAC-SHA2-256-128, RFC 8221 MUST

def ah_view(packet):                          # RFC 4302 s.3.3.3.1: zero the mutable fields
    v = bytearray(packet)
    v[1] = 0                                  # DSCP and ECN
    v[6:8] = b'\x00\x00'                      # flags and fragment offset
    v[8] = 0                                  # time to live
    v[10:12] = b'\x00\x00'                    # header checksum
    v[20 + 12:20 + 12 + ICV_LEN] = bytes(ICV_LEN)   # the ICV itself
    return bytes(v)

def add_ah(src, dst, next_header, payload, seq):
    ah = bytearray()
    ah += bytes([next_header, (12 + ICV_LEN) // 4 - 2]) + b'\x00\x00'
    ah += SPI.to_bytes(4, 'big') + seq.to_bytes(4, 'big') + bytes(ICV_LEN)
    packet = ipv4(src, dst, 51, bytes(ah) + payload)       # 51: AH
    icv = hmac.new(KEY, ah_view(packet), hashlib.sha256).digest()[:ICV_LEN]
    packet[20 + 12:20 + 12 + ICV_LEN] = icv
    return packet

def ah_ok(packet):
    icv = bytes(packet[32:32 + ICV_LEN])
    return hmac.compare_digest(icv, hmac.new(KEY, ah_view(packet), hashlib.sha256)
                               .digest()[:ICV_LEN])

tcp = b'\x1f\x90\x00\x50' + bytes(16) + b'GET /marks HTTP/1.1\r\n'
p = add_ah('10.1.4.7', '10.2.8.1', 6, tcp, seq=1)
print()
print('THE AH HEADER, AS BUILT (transport mode, after the 20-byte IPv4 header)')
print('  next header  %d (TCP)' % p[20])
print('  payload len  %d, so AH is (%d + 2) x 4 = %d bytes' % (p[21], p[21], (p[21] + 2) * 4))
print('  reserved     %s' % p[22:24].hex())
print('  SPI          0x%08X' % int.from_bytes(p[24:28], 'big'))
print('  sequence     %d' % int.from_bytes(p[28:32], 'big'))
print('  ICV          %s (%d bits)' % (p[32:48].hex(), ICV_LEN * 8))
print('  IP protocol field says %d, AH; total length %d' % (p[9], int.from_bytes(p[2:4], 'big')))
print('  ICV verifies:', ah_ok(p))

def tamper(label, change):
    q = bytearray(p)
    change(q)
    q[10:12] = b'\x00\x00'
    q[10:12] = checksum(bytes(q[:20])).to_bytes(2, 'big')    # a valid IPv4 header again
    print('  %-46s ICV verifies: %s' % (label, ah_ok(q)))

print()
print('WHAT HAPPENS ON THE WAY')
tamper('a router decrements TTL (mutable, zeroed)', lambda q: q.__setitem__(8, q[8] - 1))
tamper('a router re-marks DSCP (mutable, zeroed)', lambda q: q.__setitem__(1, 0xB8))
tamper('someone changes the source address',
       lambda q: q.__setitem__(slice(12, 16), bytes([10, 1, 4, 9])))
tamper('a NAT rewrites the source to 198.51.100.1',
       lambda q: q.__setitem__(slice(12, 16), bytes([198, 51, 100, 1])))
tamper('someone changes one byte of the request', lambda q: q.__setitem__(-5, ord('X')))
tamper('someone changes the SPI', lambda q: q.__setitem__(27, q[27] ^ 1))

# ---- the anti-replay window: RFC 4302 s.3.4.3, window of 32 --------------------------------
class Window:
    def __init__(self, size=32):
        self.size, self.right, self.seen = size, 0, set()

    def check(self, seq, icv_good=True):
        if seq == 0:
            return 'reject: sequence numbers start at 1'
        if seq <= self.right - self.size:
            return 'reject: left of the window (window is %d to %d)' % (
                self.right - self.size + 1, self.right)
        if seq in self.seen:
            return 'reject: duplicate'
        if not icv_good:
            return 'reject: ICV fails, window NOT updated'
        if seq > self.right:
            self.right = seq
        self.seen = {s for s in self.seen | {seq} if s > self.right - self.size}
        return 'accept (window now %d to %d)' % (max(1, self.right - self.size + 1), self.right)

print()
print('THE ANTI-REPLAY WINDOW, 32 PACKETS WIDE, AS PACKETS ARRIVE')
w = Window()
for seq, good in ((1, True), (2, True), (5, True), (3, True), (3, True), (40, True),
                  (7, True), (9, True), (20, True), (20, True), (41, False), (41, True),
                  (38, True), (0, True)):
    print('  sequence %2d %-9s -> %s' % (seq, '' if good else '(forged)', w.check(seq, good)))
munotes.in485

The Authentication Header

HMAC-SHA-256 reproduces RFC 4231 test case 2: True

THE AH HEADER, AS BUILT (transport mode, after the 20-byte IPv4 header)
  next header  6 (TCP)
  payload len  5, so AH is (5 + 2) x 4 = 28 bytes
  reserved     0000
  SPI          0x0000A001
  sequence     1
  ICV          f66cdf5054061ed402ec781e4f96a4a6 (128 bits)
  IP protocol field says 51, AH; total length 89
  ICV verifies: True

WHAT HAPPENS ON THE WAY
  a router decrements TTL (mutable, zeroed)      ICV verifies: True
  a router re-marks DSCP (mutable, zeroed)       ICV verifies: True
  someone changes the source address             ICV verifies: False
  a NAT rewrites the source to 198.51.100.1      ICV verifies: False
  someone changes one byte of the request        ICV verifies: False
  someone changes the SPI                        ICV verifies: False

THE ANTI-REPLAY WINDOW, 32 PACKETS WIDE, AS PACKETS ARRIVE
  sequence  1           -> accept (window now 1 to 1)
  sequence  2           -> accept (window now 1 to 2)
  sequence  5           -> accept (window now 1 to 5)
  sequence  3           -> accept (window now 1 to 5)
  sequence  3           -> reject: duplicate
  sequence 40           -> accept (window now 9 to 40)
  sequence  7           -> reject: left of the window (window is 9 to 40)
  sequence  9           -> accept (window now 9 to 40)
  sequence 20           -> accept (window now 9 to 40)
  sequence 20           -> reject: duplicate
  sequence 41 (forged)  -> reject: ICV fails, window NOT updated
  sequence 41           -> accept (window now 10 to 41)
  sequence 38           -> accept (window now 10 to 41)
  sequence  0           -> reject: sequence numbers start at 1
munotes.in486

The Authentication Header

What the run establishes, in order.

The header is exactly RFC 4302's. Next header 6, TCP; payload length 5, which means (5 + 2) × 4 = 28 bytes; reserved zero; the SPI; sequence number 1; and a 128-bit ICV. The IP header's protocol field is now 51.

What routers may change survives. Decrementing the time to live and re-marking DSCP, each followed by a correct new header checksum, leave the ICV valid, because all three fields were zeroed when it was computed.

Everything else is caught. A changed source address fails. A NAT rewriting the source address fails in exactly the same way, which is the whole story of AH's decline. A single changed byte of the web request fails, and so does a changed SPI.

The window rejects what it should and only what it should. Packets 1, 2, 5 and a late 3 are accepted; the second 3 is a duplicate. Packet 40 moves the window to 9 to 40, so packet 7 is now too old, while 9, at the window's left edge, and 20, inside it, are accepted. A forged 41 fails its ICV and does not move the window; the genuine 41 then does. A late 38 is still accepted, and 0, which no sender ever uses, is refused.

Why AH is fading

AH cannot pass through NAT. RFC 3715, on the incompatibilities between IPsec and network address translation, puts it first: because AH includes the source and destination addresses in its keyed check, "NAT or reverse NAT devices making changes to address fields will invalidate the message integrity check". ESP does not include the outer addresses, so it does not have this problem. Almost every home and campus network uses NAT.

ESP can do AH's job. ESP with its integrity service and the NULL cipher gives integrity and origin authentication without encryption. RFC 8221 keeps NULL encryption as a MUST "to enable the use of ESP with only authentication, which is preferred over AH due to NAT traversal". And ESP can cross a NAT by travelling inside UDP, on port 4500, as RFC 3948 specifies.

munotes.in487

The Authentication Header

So the standards demoted it. RFC 4301 requires ESP and only permits AH, because "there are very few contexts in which ESP cannot provide the requisite security services". What AH still offers that ESP does not is protection of the IP header itself, which matters only where the addresses must be proved and no NAT is in the path.

Algorithms today, from RFC 8221: HMAC-SHA2-256-128 MUST be implemented; HMAC-SHA2-512-256 SHOULD; HMAC-SHA1-96 is MUST-, required now but on its way out; HMAC-MD5-96 MUST NOT be used.

Distinctions that carry marks

AHESP
IP protocol number5150
Confidentialitynoneyes
Integrity and originyesyes, if selected
Covers the IP headeryes, apart from mutable fieldsno, only what follows the ESP header
Works through NATnoyes, inside UDP (RFC 3948)
Status (RFC 4301)MAYMUST
Mutable fieldImmutable field
In the ICVas zerosas it is
IPv4 examplesTTL, header checksum, DSCP, ECN, flags, fragment offsetsource and destination addresses, protocol, total length, identification
If changed in transitICV still verifiesICV fails

What beginners get wrong here

Saying AH encrypts. It encrypts nothing; the whole packet travels readable.

Saying AH covers the entire IP header. It covers the header except the mutable fields, which are zeroed for the computation.

Getting Payload Length wrong. It is the length of AH in 32-bit words minus 2, not the length of the data.

Updating the window before checking the ICV. The window moves only for packets whose ICV verifies; otherwise forged packets could push it past genuine ones.

Treating AH as a current choice. It is optional, it fails through NAT, and ESP with NULL encryption does its job.

Quick revision

  • AH (RFC 4302): IP protocol 51; integrity, data origin authentication, anti-replay; no confidentiality.
  • Fields: Next Header (8), Payload Length (8, in 32-bit words minus 2), Reserved (16), SPI (32), Sequence Number (32), ICV (variable).
  • ICV covers the whole packet; mutable fields zeroed: DSCP and ECN, flags, fragment offset, TTL, header checksum.
  • Anti-replay: sequence starts at 1; window at least 32, 64 preferred; left of window, discard; duplicate, discard; otherwise check the ICV and only then update the window.
  • Transport mode: covers the payload and the original header's immutable fields. Tunnel mode: covers the whole inner packet and the new header's immutable fields.
  • Decline: breaks through NAT (RFC 3715); ESP with NULL encryption does the same job; RFC 4301 makes AH a MAY.
  • Algorithms (RFC 8221): HMAC-SHA2-256-128 MUST; HMAC-SHA1-96 MUST-; HMAC-MD5-96 MUST NOT.

Test yourself

1. What services does AH provide, and which does it not? It provides connectionless integrity and data origin authentication for IP packets, covering the IP header's unchanging fields as well as the payload, and optionally rejection of replayed packets. It provides no confidentiality: nothing is encrypted.

munotes.in488

The Authentication Header

2. Describe the fields of the Authentication Header. Next Header, 8 bits, identifying what follows AH; Payload Length, 8 bits, giving the length of AH in 32-bit words minus 2; Reserved, 16 bits, set to zero; the Security Parameters Index, 32 bits, identifying the SA; the Sequence Number, 32 bits, incremented for every packet and used against replays; and the Integrity Check Value, of variable length in whole 32-bit words, computed over the packet.

3. Why are some IP header fields set to zero before the ICV is computed? Name them. Because routers are allowed to change them in transit, so a check over their original values would fail on every packet. In IPv4 they are the DSCP and ECN bits, the flags, the fragment offset, the time to live and the header checksum. They are replaced with zeros rather than omitted, which keeps the alignment and the length of the header unchanged for the computation.

4. An AH ICV is 96 bits long. What is the value of the Payload Length field? The fixed fields occupy three 32-bit words and the ICV three more, six in all; subtracting 2 gives a Payload Length of 4.

5. Explain the anti-replay window, and why it is updated only after the ICV verifies. The receiver records the highest sequence number accepted, N, and which numbers from N - W + 1 to N have arrived, W being the window size of at least 32. A packet numbered left of the window is discarded as too old, one already seen is discarded as a duplicate, and any other has its ICV checked; if the ICV verifies, the packet is accepted and a new highest number moves the window right. The window moves only after verification because otherwise an attacker could send a forged packet with a very high sequence number, push the window forward, and cause every genuine packet still in transit to be rejected as too old.

6. Why does AH not work through network address translation, and what is used instead? Because AH's integrity check covers the IP source and destination addresses, and a NAT device rewrites them, so every translated packet fails verification. ESP does not cover the outer IP header, so it passes through NAT, and ESP with the NULL cipher provides integrity and origin authentication without encryption, doing AH's job; RFC 4301 therefore requires ESP and makes AH optional.

Contents This chapter on its own page

munotes.in489

Chapter Seventy-Four

Encapsulating Security Payload

Syllabus topic Module 2, "IP Security: Encapsulating Security Payload"

In one line

ESP encrypts the contents of an IP packet and, usually, checks that nothing was changed, so that an observer on the path sees who is talking but nothing of what is said, not even which application is talking.

In the words an answer should use: the Encapsulating Security Payload (ESP), specified in RFC 4303, provides confidentiality, data origin authentication, connectionless integrity, an anti-replay service and limited traffic flow confidentiality for IP datagrams; its integrity check covers the ESP header, the payload and the ESP trailer, but not the IP header in front of it.

What ESP adds that AH did not

The Authentication Header proves a packet is genuine. ESP also keeps it secret. And since ESP can also provide integrity, and can be used with no encryption at all, it does everything AH does except protect the outer IP header, which is exactly the part that NAT needs to change. That is why RFC 4301 requires every IPsec implementation to support ESP and only permits AH.

The packet format

The IP header's protocol field is set to 50 for ESP. What follows is RFC 4303's layout:

FieldSizeEncryptedCovered by the ICV
Security Parameters Index32 bitsnoyes
Sequence Number32 bitsnoyes
Payload Data, beginning with an IV if the cipher needs onevariableyes (not the IV)yes
Padding0 to 255 bytesyesyes
Pad Length8 bitsyesyes
Next Header8 bitsyesyes
Integrity Check Valuevariablenoit is the check

The SPI and sequence number stay in the clear because the receiver must read them before it can do anything: the SPI to find the SA and its keys, the sequence number to reject replays before spending effort on decryption.

Padding, Pad Length and Next Header are the ESP trailer. They sit at the end and are encrypted with the payload, so an observer cannot even tell which protocol the packet carries.

Two rules about coverage come straight from RFC 4303: if integrity is selected, it covers the SPI, the sequence number, the payload and the trailer; if confidentiality is selected, the ciphertext is the payload and the trailer, but not the IV, which the receiver needs in the clear to decrypt.

Why padding exists

RFC 4303 s.2.4 gives the reasons, and a question on ESP expects them.

  1. The cipher's block size. A block cipher in CBC mode encrypts whole blocks, so payload, padding, pad length and next header together must be a multiple of the block size: 16 bytes for AES.
  2. Alignment. Even with a cipher that needs no blocks, the Pad Length and Next Header fields must end on a four-byte boundary, so that the ICV that follows is aligned.
  3. Hiding the length. Extra padding could hide the true size of the payload. RFC 4303 notes that the padding field, at most 255 bytes, is "too limited to be effective" for this and provides a separate mechanism, traffic flow confidentiality (TFC) padding, inside the payload.
munotes.in490

Encapsulating Security Payload

The sender may add from 0 to 255 bytes of padding, and by default fills it with the bytes 1, 2, 3 and so on, so the receiver can check it.

Worked example. A 20-byte payload under AES-CBC: 20 + 2 = 22 bytes of payload and trailer; the next multiple of 16 is 32; so 10 bytes of padding. Under AES-GCM, which needs only four-byte alignment: 22 rounds up to 24, so 2 bytes of padding.

ESP in the two modes

Two packet diagrams. In transport mode: the original IP header, the ESP header, TCP, data, the ESP trailer and the ICV; a bracket above marks TCP, data and trailer as encrypted, and a bracket below marks the ESP header through the trailer as authenticated. In tunnel mode: a new IP header, the ESP header, the original IP header, TCP, data, the ESP trailer and the ICV; the encrypted bracket covers the whole original packet and the trailer, and the authenticated bracket runs from the ESP header to the trailer. A note says the outer IP header is covered by neither.

Figure 74.1 ESP encrypts what follows its header and authenticates from its header to its trailer; the IP header in front is covered by neither.

Transport mode. The ESP header follows the original IP header. The TCP or UDP segment and the trailer are encrypted; the original IP header is neither encrypted nor covered by ESP's check. An observer sees the real addresses, and nothing else.

Tunnel mode. The whole original packet, header included, is encrypted inside a new packet whose header names the two IPsec endpoints. An observer sees only that two gateways are exchanging ESP; which hosts behind them are talking, and on which ports, is hidden. This is the standard arrangement for a VPN.

The run: ESP built byte by byte

The listing assembles ESP packets exactly as RFC 4303 lays them out, with HMAC-SHA2-256-128 as the integrity check. It works the padding for the two ciphers RFC 8221 requires, then shows what an observer sees, runs ESP with the NULL cipher, and passes a packet through a NAT. The cipher itself is a stand-in, but every size is the size AES would give.

# ESP built byte by byte: padding, what an observer sees, NULL encryption, and NAT.
import hashlib, hmac, random

rng = random.Random(740)
KEY_E, KEY_A, SPI = rng.randbytes(16), rng.randbytes(32), 0x0000B002

def keystream(iv, n):                     # a stand-in for AES; the sizes below are AES's
    return b''.join(hashlib.sha256(KEY_E + iv + i.to_bytes(4, 'big')).digest()
                    for i in range(n // 32 + 1))[:n]

def esp(payload, next_header, seq, block=16, iv_len=16, cipher=True):
    """RFC 4303: SPI, sequence, [IV], payload, padding, pad length, next header, ICV."""
    pad = (-(len(payload) + 2)) % block
    trailer = bytes(range(1, pad + 1)) + bytes([pad, next_header])   # default padding 1, 2, 3
    plain = payload + trailer
    iv = rng.randbytes(iv_len) if cipher else b''
    body = iv + (bytes(a ^ b for a, b in zip(plain, keystream(iv, len(plain)))) if cipher
                 else plain)
    head = SPI.to_bytes(4, 'big') + seq.to_bytes(4, 'big')
    icv = hmac.new(KEY_A, head + body, hashlib.sha256).digest()[:16]  # HMAC-SHA2-256-128
    return head + body + icv, pad

def esp_open(packet, iv_len=16, cipher=True):
    head_body, icv = packet[:-16], packet[-16:]
    if not hmac.compare_digest(icv, hmac.new(KEY_A, head_body, hashlib.sha256).digest()[:16]):
        return None
    body = head_body[8:]
    iv, data = body[:iv_len], body[iv_len:]
    plain = bytes(a ^ b for a, b in zip(data, keystream(iv, len(data)))) if cipher else data
    pad, nh = plain[-2], plain[-1]
    return nh, plain[:-2 - pad]

# ---- 1. padding, for the ciphers RFC 8221 names ----------------------------------------
print('PADDING: payload + padding + 2 must fill whole cipher blocks, and end on 4 bytes')
print('  payload   AES-CBC (16-byte blocks)      AES-GCM (4-byte alignment only)')
for n in (1, 14, 20, 100, 1400):
    _, p_cbc = esp(bytes(n), 6, 1, block=16, iv_len=16)
    _, p_gcm = esp(bytes(n), 6, 1, block=4, iv_len=8)
    cbc = 8 + 16 + n + p_cbc + 2 + 16
    gcm = 8 + 8 + n + p_gcm + 2 + 16
    print('  %5d     pad %2d, ESP adds %2d bytes      pad %d, ESP adds %2d bytes'
          % (n, p_cbc, cbc - n, p_gcm, gcm - n))

# ---- 2. what an observer on the path sees ----------------------------------------------
tcp = (8080).to_bytes(2, 'big') + (80).to_bytes(2, 'big') + bytes(16) + b'GET /marks HTTP/1.1\r\n'
packet, _ = esp(tcp, 6, seq=7)
print()
print('WHAT AN OBSERVER SEES OF ONE ESP PACKET (%d bytes)' % len(packet))
print('  SPI %s  sequence %d  : in clear, so the receiver can find the SA and check replays'
      % (packet[:4].hex(), int.from_bytes(packet[4:8], 'big')))
print('  the TCP ports 8080 and 80 are visible   :', tcp[:4] in packet)
print('  the text "GET /marks" is visible        :', b'GET /marks' in packet)
print('  the receiver recovers protocol and data :', esp_open(packet) == (6, tcp))

# ---- 3. ESP with NULL encryption: integrity without secrecy ----------------------------
null, _ = esp(tcp, 6, seq=8, block=4, iv_len=0, cipher=False)
print()
print('ESP WITH THE NULL CIPHER (RFC 8221: MUST be implemented)')
print('  the text is visible                     :', b'GET /marks' in null)
print('  the receiver verifies and recovers it   :',
      esp_open(null, iv_len=0, cipher=False) == (6, tcp))
altered = bytearray(null)
altered[30] ^= 1
print('  one changed byte is detected            :', esp_open(bytes(altered), 0, False) is None)

# ---- 4. a NAT on the path ---------------------------------------------------------------
def outer_ip(src, dst, protocol, payload):
    h = bytearray(20)
    h[0], h[8], h[9] = 0x45, 64, protocol
    h[2:4] = (20 + len(payload)).to_bytes(2, 'big')
    h[12:16], h[16:20] = bytes(map(int, src.split('.'))), bytes(map(int, dst.split('.')))
    return bytes(h) + payload

sent = outer_ip('10.1.4.7', '203.0.113.2', 50, packet)
translated = sent[:12] + bytes([198, 51, 100, 1]) + sent[16:]      # the NAT's new source
print()
print('A NAT REWRITES THE SOURCE ADDRESS 10.1.4.7 TO 198.51.100.1')
print('  the outer header changed                :', translated[12:16] != sent[12:16])
print('  ESP still verifies and decrypts         :', esp_open(translated[20:]) == (6, tcp))
print('  (AH covers the source address, so the same NAT breaks it: the previous chapter)')
munotes.in491

Encapsulating Security Payload

PADDING: payload + padding + 2 must fill whole cipher blocks, and end on 4 bytes
  payload   AES-CBC (16-byte blocks)      AES-GCM (4-byte alignment only)
      1     pad 13, ESP adds 55 bytes      pad 1, ESP adds 35 bytes
     14     pad  0, ESP adds 42 bytes      pad 0, ESP adds 34 bytes
     20     pad 10, ESP adds 52 bytes      pad 2, ESP adds 36 bytes
    100     pad 10, ESP adds 52 bytes      pad 2, ESP adds 36 bytes
   1400     pad  6, ESP adds 48 bytes      pad 2, ESP adds 36 bytes

WHAT AN OBSERVER SEES OF ONE ESP PACKET (88 bytes)
  SPI 0000b002  sequence 7  : in clear, so the receiver can find the SA and check replays
  the TCP ports 8080 and 80 are visible   : False
  the text "GET /marks" is visible        : False
  the receiver recovers protocol and data : True

ESP WITH THE NULL CIPHER (RFC 8221: MUST be implemented)
  the text is visible                     : True
  the receiver verifies and recovers it   : True
  one changed byte is detected            : True

A NAT REWRITES THE SOURCE ADDRESS 10.1.4.7 TO 198.51.100.1
  the outer header changed                : True
  ESP still verifies and decrypts         : True
  (AH covers the source address, so the same NAT breaks it: the previous chapter)
munotes.in492

Encapsulating Security Payload

What the run establishes, in order.

Padding depends on the cipher. Under AES-CBC every payload is padded to a multiple of 16: a 1-byte payload gets 13 bytes of padding, a 20-byte payload 10, and a 14-byte payload none, because 14 + 2 is exactly 16. Under AES-GCM only four-byte alignment is needed, so padding never exceeds 3 bytes. Counting the SPI, sequence number, IV, padding, trailer and ICV, ESP adds 42 to 55 bytes under AES-CBC and 34 to 36 under AES-GCM, one reason RFC 8221 now makes GCM a MUST.

An observer learns almost nothing. Of an 88-byte packet only the SPI and the sequence number are readable. Even the TCP ports are hidden, which AH could never do; the web request is invisible; and the receiver recovers the protocol and the data exactly.

ESP with the NULL cipher is AH's job done by ESP. The text is readable, but the receiver verifies it, and a single changed byte is detected.

A NAT does not break ESP. Rewriting the outer source address leaves ESP verifying and decrypting correctly, because ESP's check starts at the ESP header. In the previous chapter the same rewrite broke AH.

ESP against AH

AHESP
IP protocol number5150
Confidentialitynoyes
Integrity and data origin authenticationyesyes, when selected
Anti-replayyesyes
Limited traffic flow confidentialitynoyes, in tunnel mode and with TFC padding
Covers the IP header in front of ityes, apart from mutable fieldsno
Hides the transport protocol and portsnoyes
Survives NATnoyes, and inside UDP on port 4500 (RFC 3948)
Required by RFC 4301MAYMUST
munotes.in493

Encapsulating Security Payload

Why the modern world uses ESP alone

It does everything needed. Confidentiality and integrity together, or integrity alone with the NULL cipher.

It survives NAT. AH does not, and NAT is everywhere.

Combined algorithms do both jobs in one pass. AES in GCM mode encrypts and authenticates at once, producing its own integrity tag. RFC 8221 made AES-GCM a MUST for ESP; used with it, ESP needs no separate integrity algorithm.

Current algorithm requirements, RFC 8221 (October 2017):

AlgorithmWhat it doesRFC 8221
AES-GCM with a 16-byte tagencryption with integrityMUST
AES-CBCencryptionMUST
NULLno encryptionMUST
ChaCha20-Poly1305encryption with integritySHOULD
AES-CCM with an 8-byte tagencryption with integritySHOULD, for IoT devices
Triple DESencryptionSHOULD NOT
DESencryptionMUST NOT
HMAC-SHA2-256-128integrityMUST
HMAC-SHA2-512-256integritySHOULD
HMAC-SHA1-96integrityMUST-
AES-XCBC-96integritySHOULD, for IoT devices
HMAC-MD5-96integrityMUST NOT
none, with a cipher that has no integrityintegrityMUST NOT

The textbooks' ESP, with DES or Triple DES and HMAC-MD5-96, is today's MUST NOT and SHOULD NOT.

What beginners get wrong here

Saying ESP always authenticates. Integrity is a service ESP provides when it is selected; an SA without it is legal but unwise. RFC 8221 forbids the combination of a non-combined cipher with no integrity algorithm.

Saying ESP protects the IP header. In transport mode the IP header is outside ESP; in tunnel mode the outer header is. Neither is encrypted or covered by ESP's check.

Forgetting what padding is for. It completes the cipher's blocks and aligns the trailer; hiding the length is TFC's job.

Thinking the sequence number is encrypted. The SPI and sequence number must be read before decryption and travel in the clear, protected only by the integrity check.

Quoting DES and MD5 as ESP's algorithms. RFC 8221 says DES and HMAC-MD5 must not be used.

Quick revision

  • ESP (RFC 4303): IP protocol 50; confidentiality, integrity, origin authentication, anti-replay, limited traffic flow confidentiality.
  • Format: SPI, Sequence Number (both clear), Payload Data (with IV), Padding (0 to 255), Pad Length, Next Header, ICV.
  • Integrity covers SPI to trailer; encryption covers payload and trailer; the IP header in front is covered by neither.
  • Padding: block size, four-byte alignment; not for hiding length (use TFC padding). AES-CBC pads to 16; AES-GCM to 4.
  • Transport hides the payload and ports; tunnel hides the whole inner packet, real addresses included.
  • ESP alone: does AH's job with NULL encryption, survives NAT (RFC 3948, UDP port 4500), and AES-GCM does both jobs in one pass. ESP MUST, AH MAY.
  • RFC 8221: AES-GCM, AES-CBC, NULL MUST; HMAC-SHA2-256-128 MUST; 3DES SHOULD NOT; DES and HMAC-MD5 MUST NOT.
munotes.in494

Encapsulating Security Payload

Test yourself

1. Draw the ESP packet format and state which fields are encrypted and which are authenticated. It consists of the Security Parameters Index and the Sequence Number, each 32 bits; the Payload Data, beginning with an initialisation vector when the cipher needs one; the Padding, from 0 to 255 bytes; the 8-bit Pad Length; the 8-bit Next Header; and the Integrity Check Value. The payload and the trailer (padding, pad length and next header) are encrypted, the IV excepted. The integrity check covers everything from the SPI to the end of the trailer. The SPI and sequence number are not encrypted, and the IP header before ESP is covered by neither.

2. Give the reasons for the Padding field. To make the plaintext, payload plus padding plus pad length plus next header, a whole number of blocks for a block cipher; and to make the trailer end on a four-byte boundary so that the ICV is aligned, even when the cipher has no block size. Extra padding could also hide the payload's length, but RFC 4303 says the field is too limited for that and provides separate TFC padding.

3. How much padding does a 20-byte payload need under AES-CBC, and under AES-GCM? Under AES-CBC, 20 bytes of payload and 2 bytes of trailer make 22, and the next multiple of the 16-byte block is 32, so 10 bytes of padding. Under AES-GCM only four-byte alignment is needed, so 22 is rounded up to 24, and 2 bytes of padding are added.

4. Compare ESP with AH. ESP provides confidentiality as well as integrity, origin authentication and anti-replay, and can hide the transport protocol and ports; AH provides no confidentiality. AH's check covers the unchanging fields of the IP header in front of it, which ESP's does not. Because of that, AH fails through NAT while ESP passes through it. RFC 4301 requires ESP and makes AH optional.

5. Why is ESP now used on its own? Because it can do everything required: with the NULL cipher it provides integrity and origin authentication alone, which is AH's job; it passes through NAT, which AH cannot; and combined algorithms such as AES-GCM give encryption and integrity in one operation. RFC 8221 keeps NULL encryption mandatory for exactly this purpose.

6. What does an observer on the path see of an ESP packet in tunnel mode? The outer IP header, which names only the two IPsec endpoints, usually gateways, and the SPI and sequence number. The original packet, including the real source and destination addresses, the transport protocol, the ports and the data, is encrypted.

Contents This chapter on its own page

munotes.in495

Chapter Seventy-Five

Combining Security Associations

Syllabus topic Module 2, "IP Security: Combining Security Associations"

In one line

One security association does one job with one protocol between two points, so when a packet needs two jobs, or protection between two different pairs of points, it gets two SAs, one wrapped around the other.

In the words an answer should use: because a single SA applies either AH or ESP but not both, and runs between one pair of endpoints, a security policy that needs a combination of services, or protection over different segments of a path, is met by a security association bundle, a sequence of SAs through which traffic is processed; SAs are combined by transport adjacency, applying more than one protocol to the same IP datagram without tunnelling, or by iterated tunnelling, applying several layers of protocol through nested IP tunnels.

Why one SA is sometimes not enough

Two limits of a single SA force combinations.

One protocol per SA. RFC 4301: "Security services are afforded to an SA by the use of AH, or ESP, but not both." A policy wanting AH's protection of the IP header and ESP's encryption needs two SAs.

One pair of endpoints per SA. A teacher working from home may need protection to the campus gateway, which checks who may enter the campus network, and to the server itself, so that even staff and machines inside the campus cannot read the data. Those are two different pairs of endpoints, and so two SAs.

The SA bundle, and its two forms

RFC 2401 called a sequence of SAs that traffic must pass through, to satisfy a policy, an SA bundle. The SAs in a bundle may end at different points: one at a gateway, another at a host behind it.

Transport adjacency. More than one security protocol is applied to the same IP datagram, without tunnelling. Both SAs run between the same two hosts. RFC 2401 notes that only one level of this is useful: the processing all happens at the one destination, so nesting deeper adds nothing.

Iterated tunnelling. Security protocols are applied in layers, through IP tunnels, and each tunnel can begin and end at a different place along the path. This allows many levels of nesting. RFC 2401 describes three arrangements:

  1. Both endpoints the same. An inner and an outer tunnel between the same two hosts, which is legal but of little use.
  2. One endpoint the same. A host tunnels to a remote gateway, and inside that to a host behind it.
  3. Neither endpoint the same. Two gateways tunnel between themselves, and two hosts behind them tunnel end to end inside it.

Authentication plus confidentiality: three ways

A policy wanting both services can be met in three ways, and textbooks compare them.

munotes.in496

Combining Security Associations

ESP with its integrity option. One SA does both jobs. The ICV covers the ESP packet, but not the IP header in front of it. This is the arrangement in use today, usually with AES-GCM, which does both jobs in one operation.

Transport adjacency: ESP inside, AH outside. An inner ESP SA encrypts the payload, and an outer AH SA authenticates the result. The headers read [IP][AH][ESP][upper]. AH's check covers the ESP packet and the unchanging parts of the IP header, so the addresses are authenticated too, which ESP alone cannot do. RFC 2401 fixes the order: "first ESP, then AH are applied to the packet". The receiver then checks AH first, and so rejects a forged or altered packet before spending any effort decrypting it.

A transport-tunnel bundle: AH inside, ESP outside. An inner AH SA in transport mode authenticates the original packet, and an outer ESP SA in tunnel mode encrypts the whole authenticated packet. Authentication comes before encryption, which has two consequences: the authentication data is itself hidden and protected by the encryption, and the destination can keep the authenticated packet, with its authentication, after the encryption is removed, to check later.

The four basic combinations

RFC 2401 s.4.5 listed four combinations that every compliant host or gateway had to be able to generate and process.

Four small network diagrams. Case 1: hosts H1 and H2 across the Internet joined by one or more SAs, transport or tunnel. Case 2: H1 behind gateway SG1 and H2 behind gateway SG2, with a tunnel SA between the two gateways. Case 3: the same network, with the gateway tunnel and also an end-to-end SA from H1 to H2. Case 4: H1 on the Internet, gateway SG2 and H2 behind it, with a tunnel SA from H1 to SG2 and an end-to-end SA from H1 to H2.

Figure 75.1 The four basic combinations of RFC 2401 section 4.5.

Case 1: end-to-end security between two hosts. Both hosts implement IPsec and share one or more SAs. The possible header stacks are, in transport mode, [IP1][AH][upper], [IP1][ESP][upper] or [IP1][AH][ESP][upper], and in tunnel mode [IP2][AH][IP1][upper] or [IP2][ESP][IP1][upper].

Case 2: a virtual private network between gateways. Only the two security gateways implement IPsec, and only tunnel mode is needed: [IP2][AH][IP1][upper] or [IP2][ESP][IP1][upper]. The hosts behind them need nothing.

Case 3: case 2 plus end-to-end security. The gateways keep their tunnel, and the hosts add their own SA inside it. Nothing new is required of anyone, except that the gateways must let the hosts' own IPsec and key-management traffic through.

Case 4: a remote host reaching a host behind a gateway. The remote host, H1, sets up a tunnel to the gateway, SG2, and an end-to-end SA to the server, H2. RFC 2401: "the sender MUST apply the transport header before the tunnel header". The end-to-end SA is applied first, innermost; the tunnel second, outermost.

The run: case 4, and transport adjacency

The listing builds case 4 with real nesting: an ESP SA from the remote host H1 to the server H2, carried inside an ESP tunnel from H1 to the gateway SG2. Each node has its own key ring, holding only the SAs it is party to. Then it builds transport adjacency in the order RFC 2401 mandates, and attacks it.

munotes.in497

Combining Security Associations

# Two SAs on one packet: an end-to-end SA inside a tunnel (RFC 2401 case 4), and AH over ESP.
import hashlib, hmac, random

rng = random.Random(750)

def ip(src, dst, proto, payload):
    return {'src': src, 'dst': dst, 'proto': proto, 'payload': payload}

def size(p):
    inner = p['payload']
    return 20 + (size(inner) if isinstance(inner, dict) else len(inner))

def stream(key, iv, n):
    return b''.join(hashlib.sha256(key + iv + i.to_bytes(4, 'big')).digest()
                    for i in range(n // 32 + 1))[:n]

def serial(x):                            # bytes of whatever an ESP layer carries
    if isinstance(x, dict):
        return repr(sorted(x.items())).encode()
    return x

class ESP:                                # one SA: ESP with an HMAC-SHA2-256-128 ICV
    def __init__(self, spi, key):
        self.spi, self.key, self.opened = spi, key, 0

    def seal(self, inner, next_header):
        iv = rng.randbytes(16)
        data = serial(inner)
        ct = bytes(a ^ b for a, b in zip(data, stream(self.key, iv, len(data))))
        body = self.spi.to_bytes(4, 'big') + iv + ct
        return {'esp': self.spi, 'nh': next_header, 'inner': inner, 'bytes': body,
                'icv': hmac.new(self.key, body, hashlib.sha256).digest()[:16]}

def unseal(sa, e):
    if not hmac.compare_digest(e['icv'], hmac.new(sa.key, e['bytes'], hashlib.sha256)
                               .digest()[:16]):
        return None
    sa.opened += 1
    return e['inner']

def describe(p):
    parts, x = [], p
    while True:
        parts.append('[IP %s->%s]' % (x['src'], x['dst']))
        e = x['payload']
        parts.append('[ESP 0x%X]' % e['esp'])
        if isinstance(e['inner'], dict) and 'src' in e['inner']:
            x = e['inner']
            continue
        parts.append('[TCP + data, sealed]')
        return ' '.join(parts)

def length(p):                           # IP 20 + ESP (8 + IV 16 + data + pad + 2 + ICV 16)
    inner = p['payload']['inner']
    n = length(inner) if isinstance(inner, dict) else len(inner)
    return 20 + 8 + 16 + n + (-(n + 2)) % 16 + 2 + 16

# ---- RFC 2401 case 4: remote host H1, gateway SG2, server H2 behind it --------------------
H1, SG2, H2 = '203.0.113.50', '198.51.100.1', '10.2.8.1'
tunnel = ESP(0x7001, rng.randbytes(32))        # shared by H1 and SG2 only
e2e = ESP(0x8001, rng.randbytes(32))           # shared by H1 and H2 only
data = b'\x1f\x90\x00\x50' + bytes(16) + b'POST /internal-marks'

inner = ip(H1, H2, 50, e2e.seal(data, 6))              # 1. the end-to-end SA, transport mode
outer = ip(H1, SG2, 50, tunnel.seal(inner, 4))         # 2. then the tunnel SA around it
print('CASE 4: A REMOTE HOST, THROUGH A GATEWAY, TO A SERVER (RFC 2401 s.4.5)')
print('  the sender applies the end-to-end SA FIRST, then the tunnel SA')
print('  on the Internet  %3d bytes  %s' % (length(outer), describe(outer)))

KEYS = {'SG2': {0x7001: tunnel}, 'H2': {0x8001: e2e}}       # each node's own SAs

def open_layer(node, packet):
    sa = KEYS[node].get(packet['payload']['esp'])
    return unseal(sa, packet['payload']) if sa else None

at_gateway = open_layer('SG2', outer)
print('  SG2 removes the tunnel SA 0x7001   :', at_gateway is not None)
print('  SG2 has an SA for the inner 0x8001 :', 0x8001 in KEYS['SG2'])
print('  so SG2 can read the TCP data       :', open_layer('SG2', at_gateway) is not None)
print('  on the intranet  %3d bytes  %s' % (length(at_gateway), describe(at_gateway)))
print('  H2 removes the end-to-end SA 0x8001:', open_layer('H2', at_gateway) == data)
print('  so the data was sealed on the Internet AND on the intranet, and the gateway')
print('  checked who H1 was without ever seeing what H1 sent')

# ---- transport adjacency: ESP first, then AH over it (RFC 2401 case 1, header stack 3) ----
print()
print('TRANSPORT ADJACENCY: [IP][AH][ESP][TCP + data] (RFC 2401: first ESP, then AH)')
AH_KEY = rng.randbytes(32)
esp_only = ESP(0x9001, rng.randbytes(32))
sealed = esp_only.seal(data, 6)
header = (H1 + '>' + H2).encode()               # the immutable parts of the IP header
ah_icv = hmac.new(AH_KEY, header + sealed['bytes'] + sealed['icv'],
                  hashlib.sha256).digest()[:16]

def receive(header, sealed, ah_icv):
    expect = hmac.new(AH_KEY, header + sealed['bytes'] + sealed['icv'], hashlib.sha256)
    good = hmac.compare_digest(ah_icv, expect.digest()[:16])
    if not good:
        return 'AH fails: dropped before any decryption'
    return 'AH verifies, then ESP decrypts: %r' % unseal(esp_only, sealed)[-20:]

print('  genuine packet   :', receive(header, sealed, ah_icv))
forged = dict(sealed, bytes=sealed['bytes'][:-1] + b'X')
print('  forged packet    :', receive(header, forged, ah_icv))
print('  spoofed source   :', receive(('203.0.113.99>' + H2).encode(), sealed, ah_icv))
print('  decryptions performed by this receiver: %d' % esp_only.opened)
munotes.in498

Combining Security Associations

CASE 4: A REMOTE HOST, THROUGH A GATEWAY, TO A SERVER (RFC 2401 s.4.5)
  the sender applies the end-to-end SA FIRST, then the tunnel SA
  on the Internet  172 bytes  [IP 203.0.113.50->198.51.100.1] [ESP 0x7001] [IP 203.0.113.50->10.2.8.1] [ESP 0x8001] [TCP + data, sealed]
  SG2 removes the tunnel SA 0x7001   : True
  SG2 has an SA for the inner 0x8001 : False
  so SG2 can read the TCP data       : False
  on the intranet  108 bytes  [IP 203.0.113.50->10.2.8.1] [ESP 0x8001] [TCP + data, sealed]
  H2 removes the end-to-end SA 0x8001: True
  so the data was sealed on the Internet AND on the intranet, and the gateway
  checked who H1 was without ever seeing what H1 sent

TRANSPORT ADJACENCY: [IP][AH][ESP][TCP + data] (RFC 2401: first ESP, then AH)
  genuine packet   : AH verifies, then ESP decrypts: b'POST /internal-marks'
  forged packet    : AH fails: dropped before any decryption
  spoofed source   : AH fails: dropped before any decryption
  decryptions performed by this receiver: 1

What the run establishes, in order.

The sender applies the SAs inside out. First the end-to-end SA 0x8001, then the tunnel SA 0x7001. On the Internet the packet is 172 bytes, and an observer sees only that H1 is talking to the gateway.

The gateway removes exactly one layer. SG2 holds the tunnel SA, so it verifies and removes the tunnel; it holds no SA for 0x8001, so it cannot open what is inside. The packet it forwards, 108 bytes, is still sealed on the campus intranet.

munotes.in499

Combining Security Associations

The server removes the other. H2 opens SA 0x8001 and recovers the data. The gateway checked that H1 belonged, without ever seeing what H1 sent, which is precisely what case 4 is for.

Transport adjacency lets the receiver refuse forgeries cheaply. With AH outside ESP, a genuine packet verifies and decrypts. A packet with one byte of the ESP data forged, and a packet whose source address is spoofed, both fail AH and are dropped before any decryption: the receiver performed exactly one decryption, for the one genuine packet.

What the current architecture says

RFC 4301 no longer requires any of this. Its s.4.3: "This document does not require support for nested security associations or for what RFC 2401 called 'SA bundles'." Such arrangements "still can be effected by appropriate configuration of both the SPD and the local forwarding functions", but management of them is now "potentially more complex and less assured". RFC 4301's Appendix E gives an example of configuring nested SAs.

Why the change is reasonable. ESP with integrity, and especially ESP with a combined algorithm such as AES-GCM, gives confidentiality and integrity in one SA, and AH, the usual reason for bundling, is now optional. The one combination still common in practice is the one the run shows: an end-to-end SA inside a VPN tunnel.

Distinctions that carry marks

Transport adjacencyIterated tunnelling
Howseveral protocols on the same datagram, no new IP headerprotocols applied through nested tunnels, each with a new IP header
Endpointsthe same two hosts for every SAeach tunnel may begin and end at a different point
Levels of nestingone is usefulmany
Example[IP][AH][ESP][upper][IP2][ESP][IP1][ESP][upper], a tunnel with an end-to-end SA inside
ESP with integrityAH over ESP (transport adjacency)AH inside an ESP tunnel
SAsonetwotwo
IP header authenticatednoyes, the immutable fieldsthe inner header, yes
Authentication data hiddennonoyes, it is encrypted
Forgeries rejected before decryptionyes: the ICV is checked before, or alongside, decryption (RFC 4303)yes, AH is checked firstno: the outer ESP is decrypted first

What beginners get wrong here

Saying one SA can apply both AH and ESP. It cannot; that is the reason bundles exist.

Applying the tunnel before the end-to-end SA in case 4. The inner, end-to-end SA is applied first and the tunnel around it second; applied the other way, the gateway would have to decrypt data meant only for the server.

Putting AH inside ESP when the rule says ESP inside AH. In transport adjacency RFC 2401 requires ESP first, then AH, giving [IP][AH][ESP][upper].

Presenting SA bundles as current requirements. They are RFC 2401's; RFC 4301 no longer requires implementations to support them.

munotes.in500

Combining Security Associations

Quick revision

  • One SA = one protocol (AH or ESP) between one pair of endpoints; combinations need several SAs.
  • SA bundle (RFC 2401): a sequence of SAs traffic must pass through; SAs may end at different points.
  • Transport adjacency: several protocols on the same datagram, no tunnelling, one useful level; ESP first, then AH: [IP][AH][ESP][upper].
  • Iterated tunnelling: layers through nested tunnels; endpoints the same, one the same, or neither the same.
  • Authentication plus confidentiality: ESP with integrity; AH over ESP (authenticates addresses, rejects forgeries before decrypting); AH inside an ESP tunnel (authentication hidden, kept after decryption).
  • Four basic combinations (RFC 2401 s.4.5): host to host; gateway to gateway (VPN); both together; remote host to gateway plus end to end. In case 4 the end-to-end SA is applied first.
  • RFC 4301 s.4.3 no longer requires nested SAs or SA bundles; they can still be configured.

Test yourself

1. Why is it sometimes necessary to combine security associations? Because a single SA applies only one protocol, AH or ESP, and runs between one pair of endpoints. A policy needing both protocols' services, or protection over two different segments of a path, such as to a gateway and also end to end to a server behind it, needs more than one SA applied to the same traffic.

2. Distinguish transport adjacency from iterated tunnelling. Transport adjacency applies more than one security protocol to the same IP datagram without tunnelling, with every SA between the same two hosts, and only one level of it is useful. Iterated tunnelling applies security protocols in layers through nested IP tunnels, each tunnel able to start and end at a different point along the path, and allows many levels of nesting.

3. In transport adjacency, in which order are ESP and AH applied, and why is that order sensible? ESP is applied first and AH second, giving the header order IP, AH, ESP, upper layer. AH then authenticates the ESP packet together with the immutable fields of the IP header, so the addresses are authenticated as well, and the receiver checks AH first, rejecting a forged or altered packet before spending any effort decrypting it.

4. Describe the four basic combinations of security associations. Case 1: end-to-end security between two IPsec hosts, by one or more SAs in transport or tunnel mode. Case 2: a virtual private network in which two security gateways share a tunnel-mode SA and the hosts behind them need no IPsec. Case 3: case 2 together with an end-to-end SA between the two hosts inside the gateways' tunnel. Case 4: a remote host that sets up a tunnel to an organisation's gateway and, inside it, an end-to-end SA to a host behind the gateway.

munotes.in501

Combining Security Associations

5. In case 4, what can the gateway see of the traffic, and why? It can verify and remove the tunnel SA, because it shares that SA's keys with the remote host, and so it can check that the remote host is entitled to enter the network. It cannot read the data, because the data is also protected by the end-to-end SA between the remote host and the server, whose keys the gateway does not have.

6. What does RFC 4301 say about SA bundles? That it does not require support for nested security associations or for what RFC 2401 called SA bundles; such arrangements can still be achieved by configuring the Security Policy Database and the forwarding functions appropriately, but their management is potentially more complex and less assured than under RFC 2401.

Contents This chapter on its own page

munotes.in502

Chapter Seventy-Six

IPsec Key Management: Oakley, ISAKMP and IKE

Syllabus topic Module 2, "IP Security: Key Management"

In one line

Before two machines can protect traffic with IPsec they must agree on keys, and IKE is the protocol that lets them do it over an open network: a Diffie-Hellman exchange, made safe against impostors, replays and floods.

In the words an answer should use: IPsec key management determines and distributes the secret keys that security associations use. It may be manual, keys configured by hand, or automated, by the Internet Key Exchange (IKE). In the textbook's account, IKE combines Oakley, a key determination protocol based on authenticated Diffie-Hellman with cookies, nonces and chosen groups, with ISAKMP, a framework that defines message formats and procedures for establishing and managing security associations; the current version is IKEv2, RFC 7296.

Why IPsec needs key management

Every SA the previous chapters built needed keys: an encryption key, an integrity key, and a separate pair for each direction. Somebody has to put them there. RFC 4301 requires implementations to support two ways.

ManualAutomated
Howan administrator types the keys and SA details into each systema protocol negotiates SAs and derives fresh keys on demand
Suitsa few systems in a small, fixed arrangementmany systems, and SAs that come and go
New keysonly when someone retypes themwhenever an SA's lifetime runs out
Anti-replayimpractical: the sequence counter cannot be reset without new keysnormal
StandardRFC 4301 requires it be possibleIKEv2, RFC 7296

RFC 8504 is blunt about the first column: manual keying has "limited applicability and is not recommended".

What a key exchange for IPsec has to survive

The Diffie-Hellman chapters of Module 1 showed that plain Diffie-Hellman lets two parties agree a secret over an open network, and showed its three weaknesses. Every piece of Oakley and IKE exists to close one of them.

  1. No authentication. Either party can be an impostor, and a man in the middle can run a separate exchange with each side.
  2. Clogging. Diffie-Hellman is expensive. An attacker who forges source addresses can send a flood of requests, each of which makes the victim do an exponentiation and keep state, until it can do nothing else.
  3. Replay. Old messages, re-sent, might trick a party into an old exchange.

Oakley

Oakley, RFC 2412 (November 1998), is a key determination protocol built on Diffie-Hellman. Its introduction lists what it adds:

  • Cookies, "a weak address validation mechanism", against clogging.
  • Negotiated algorithms: the parties choose the encryption, key derivation and authentication methods.
  • Authentication bound to the exchange: the authentication "validates the binding of the exponentials to the identities of the parties", so a man in the middle cannot substitute his own.
  • Chosen groups: the parties can use standard groups for Diffie-Hellman or define their own.
  • Perfect forward secrecy: keys derived from fresh Diffie-Hellman values, so a later compromise of long-term keys does not expose past traffic.
munotes.in503

IPsec Key Management: Oakley, ISAKMP and IKE

It also uses nonces, random values each party contributes, so that every exchange is fresh and a replayed old message fits no current exchange.

Authentication methods. Oakley's list is pre-shared keys, which it makes REQUIRED; public keys published in the DNS; RSA keys without a certification authority, as in PGP; and RSA or DSS keys with certificates. Textbooks group these as three: digital signatures, public-key encryption, and symmetric-key encryption with a pre-shared key.

Cookies

A cookie is how a responder makes an initiator prove it can receive replies at the address it claims, before the responder does any expensive work.

The responder answers a first request not with a Diffie-Hellman value but with a cookie, a short value computed from the request. The initiator must send its request again, including the cookie. A forger using someone else's address never receives the cookie, because it goes to the address it forged, so the responder never does the work for it.

ISAKMP, RFC 2408 s.2.5.3, sets three requirements, originally Phil Karn's:

  1. The cookie must depend on the specific parties. Otherwise an attacker could obtain one real cookie and then use it with requests from any address.
  2. Only the issuer can make cookies it will accept. So it must depend on a local secret, which must not be deducible from any cookie.
  3. It must be fast to make. Otherwise the cookie itself becomes the target of the flood.

IKEv2 suggests exactly such a construction: the cookie is a version number of the secret followed by a hash of the initiator's nonce, its IP address, its SPI and the responder's secret.

ISAKMP

The Internet Security Association and Key Management Protocol, RFC 2408, is not a key exchange. It is a framework: the formats and procedures for establishing, negotiating, modifying and deleting security associations, whatever key exchange is used inside it.

Its header, fixed for every message:

FieldSizeMeaning
Initiator Cookie8 bytesthe initiator's cookie
Responder Cookie8 bytesthe responder's cookie, zero in the first message
Next Payload1 bytethe type of the first payload
Major and Minor Version4 bits eachthe protocol version
Exchange Type1 bytewhich exchange this message belongs to
Flags1 byteencryption, commit, authentication only
Message ID4 bytesidentifies a phase 2 negotiation
Length4 bytesthe whole message, header and payloads

Its payloads: security association, proposal, transform, key exchange, identification, certificate, certificate request, hash, signature, nonce, notification, delete and vendor ID.

Its exchange types: base, identity protection, authentication only, aggressive, and informational.

munotes.in504

IPsec Key Management: Oakley, ISAKMP and IKE

Two phases. First the two parties set up an ISAKMP SA to protect their own negotiations; then, under its protection, they negotiate SAs for AH or ESP. The expensive, authenticated work is done once, and each IPsec SA after it is cheap.

IKEv1

IKEv1, RFC 2409, is Oakley's exchange carried in ISAKMP's framework.

Phase 1, which authenticates the parties and builds the ISAKMP SA:

  • Main Mode, six messages, an instance of ISAKMP's identity protection exchange. The first two negotiate policy, the next two exchange Diffie-Hellman values and nonces, and the last two authenticate, encrypted, so the identities are hidden.
Main Mode (signatures)                  Aggressive Mode (signatures)

HDR, SA                  -->            HDR, SA, KE, Ni, IDii         -->
             <--  HDR, SA                            <--  HDR, SA, KE, Nr, IDir,
HDR, KE, Ni              -->                                  [CERT,] SIG_R
             <--  HDR, KE, Nr           HDR, [CERT,] SIG_I            -->
HDR*, IDii, [CERT,] SIG_I -->
             <--  HDR*, IDir, [CERT,] SIG_R        (HDR* means the payloads are encrypted)
  • Aggressive Mode, three messages. Faster, but the identities travel unprotected.

Phase 2: Quick Mode, three messages under the phase 1 SA's protection, negotiates the SAs for AH or ESP: HDR, HASH(1), SA, Ni then HDR, HASH(2), SA, Nr then HDR*, HASH(3), with optional fresh Diffie-Hellman values for perfect forward secrecy.

IKEv2, the protocol in use

IKEv2 was published as RFC 4306 in December 2005 and is now RFC 7296 (October 2014). It keeps the ideas, cuts the modes, and puts everything into request-and-response pairs.

The initial exchanges, four messages that replace Main Mode and Quick Mode together:

Initiator                                     Responder

IKE_SA_INIT   HDR, SAi1, KEi, Ni         -->
                                         <--  HDR, SAr1, KEr, Nr, [CERTREQ]
IKE_AUTH      HDR, SK {IDi, [CERT,] [CERTREQ,] [IDr,] AUTH, SAi2, TSi, TSr}   -->
                                         <--  HDR, SK {IDr, [CERT,] AUTH, SAr2, TSi, TSr}
  • IKE_SA_INIT negotiates the algorithms (SA), exchanges Diffie-Hellman values (KE) and nonces (N). Both sides can now compute the shared secret.
  • IKE_AUTH, encrypted and integrity-protected under the new keys (the SK { } notation), exchanges identities, proves them with AUTH, and sets up the first Child SA, the first ESP or AH SA, with its traffic selectors TSi and TSr, which become its SPD selectors.

Two more exchanges complete the protocol: CREATE_CHILD_SA, for further Child SAs or for rekeying, and INFORMATIONAL, for errors, deletions and checking that the peer is alive.

Where the keys come from. RFC 7296 s.2.14 derives everything from the Diffie-Hellman secret and both nonces:

SKEYSEED = prf(Ni | Nr, g^ir)
{SK_d | SK_ai | SK_ar | SK_ei | SK_er | SK_pi | SK_pr} = prf+(SKEYSEED, Ni | Nr | SPIi | SPIr)

where prf+ runs the pseudo-random function repeatedly to produce as many bytes as are needed. SK_d seeds the keys of Child SAs; SK_ai and SK_ar protect the integrity of IKE messages each way; SK_ei and SK_er encrypt them each way; SK_pi and SK_pr go into the AUTH values.

munotes.in505

IPsec Key Management: Oakley, ISAKMP and IKE

How AUTH stops the man in the middle. With a pre-shared key, the initiator's AUTH is a pseudo-random function, keyed from the shared secret, over its own first message, the responder's nonce, and a value computed with SK_pi. Its first message contains its Diffie-Hellman value. So the responder checks AUTH against the first message it received and the keys it derived. If anyone changed a Diffie-Hellman value in between, the two do not agree.

The run: IKEv2 from its formulas

The listing uses the 2048-bit group 14 that RFC 8247 requires, checked first as a safe prime, and HMAC-SHA-256 as the pseudo-random function. It runs an honest exchange, then a man in the middle, then a flood of spoofed requests with cookies off and on.

# IKEv2's first two exchanges, run: keys from RFC 7296's formulas, a MITM caught, cookies at work.
import hashlib, hmac, random

rng = random.Random(760)

# ---- Diffie-Hellman group 14 (RFC 3526), the group RFC 8247 says MUST be implemented -------
P = int(
    "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
    "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
    "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
    "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
    "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
    "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
    "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
    "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
    "3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFF"
    "FFFFFFFF", 16)
G = 2

def probably_prime(n, bases=(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37)):
    d, r = n - 1, 0
    while d % 2 == 0:
        d, r = d // 2, r + 1
    for a in bases:
        x = pow(a, d, n)
        if x in (1, n - 1):
            continue
        for _ in range(r - 1):
            x = x * x % n
            if x == n - 1:
                break
        else:
            return False
    return True

print('group 14: %d-bit prime %s, and (P - 1) / 2 prime %s: a safe prime' % (
      P.bit_length(), probably_prime(P), probably_prime((P - 1) // 2)))

def prf(key, data):                         # PRF_HMAC_SHA2_256, RFC 8247 MUST
    return hmac.new(key, data, hashlib.sha256).digest()

def prf_plus(key, seed, length):            # RFC 7296 s.2.13
    out, t, i = b'', b'', 1
    while len(out) < length:
        t = prf(key, t + seed + bytes([i]))
        out, i = out + t, i + 1
    return out[:length]

SIZES = (('SK_d', 32), ('SK_ai', 32), ('SK_ar', 32), ('SK_ei', 16), ('SK_er', 16),
         ('SK_pi', 32), ('SK_pr', 32))

def ike_keys(ni, nr, shared, spii, spir):   # RFC 7296 s.2.14
    skeyseed = prf(ni + nr, shared.to_bytes(256, 'big'))
    stream = prf_plus(skeyseed, ni + nr + spii + spir, sum(n for _, n in SIZES))
    keys, at = {}, 0
    for name, n in SIZES:
        keys[name], at = stream[at:at + n], at + n
    return keys

def half(label):                             # one side's IKE_SA_INIT contribution
    x = rng.getrandbits(256)
    return {'who': label, 'x': x, 'KE': pow(G, x, P), 'N': rng.randbytes(32),
            'SPI': rng.randbytes(8)}

def auth(psk, message1, other_nonce, sk_p, identity):   # RFC 7296 s.2.15, pre-shared key
    signed = message1 + other_nonce + prf(sk_p, identity)
    return prf(prf(psk, b'Key Pad for IKEv2'), signed)

PSK = b'campus-vpn shared secret, 32 bytes!'

# ---- 1. an honest exchange ----------------------------------------------------------------
i, r = half('initiator'), half('responder')
msg1 = i['SPI'] + i['KE'].to_bytes(256, 'big') + i['N']        # HDR, SAi1, KEi, Ni (reduced)
ki = ike_keys(i['N'], r['N'], pow(r['KE'], i['x'], P), i['SPI'], r['SPI'])
kr = ike_keys(i['N'], r['N'], pow(i['KE'], r['x'], P), i['SPI'], r['SPI'])
print()
print('IKE_SA_INIT: two messages, then both sides derive seven keys from SKEYSEED')
for name, n in SIZES:
    print('  %-6s %2d bytes  same on both sides: %s' % (name, n, ki[name] == kr[name]))
a = auth(PSK, msg1, r['N'], ki['SK_pi'], b'asha@campus-a')
print('IKE_AUTH: the responder checks the initiator\'s AUTH:',
      a == auth(PSK, msg1, r['N'], kr['SK_pi'], b'asha@campus-a'))

# ---- 2. a man in the middle ---------------------------------------------------------------
i, r, m1, m2 = half('initiator'), half('responder'), half('mallory'), half('mallory')
msg1_sent = i['SPI'] + i['KE'].to_bytes(256, 'big') + i['N']           # what the initiator sent
msg1_seen = i['SPI'] + m2['KE'].to_bytes(256, 'big') + i['N']          # what reached the responder
k_i = ike_keys(i['N'], r['N'], pow(m1['KE'], i['x'], P), i['SPI'], r['SPI'])   # with Mallory
k_r = ike_keys(i['N'], r['N'], pow(m2['KE'], r['x'], P), i['SPI'], r['SPI'])   # with Mallory
forwarded = auth(PSK, msg1_sent, r['N'], k_i['SK_pi'], b'asha@campus-a')
print()
print('A MAN IN THE MIDDLE swaps in her own Diffie-Hellman values both ways')
mallory_i = ike_keys(i['N'], r['N'], pow(i['KE'], m1['x'], P), i['SPI'], r['SPI'])
mallory_r = ike_keys(i['N'], r['N'], pow(r['KE'], m2['x'], P), i['SPI'], r['SPI'])
print('  the two sides now hold different keys :', k_i['SK_ei'] != k_r['SK_ei'])
print('  and Mallory holds both sets of keys   :', mallory_i == k_i and mallory_r == k_r)
print('  the initiator\'s AUTH, relayed, checks :',
      forwarded == auth(PSK, msg1_seen, r['N'], k_r['SK_pi'], b'asha@campus-a'))
print('  so the responder refuses the IKE SA: AUTH covers the KE it received and the keys')
print('  it derived, and without the pre-shared key Mallory cannot make a new one')

# ---- 3. cookies against a flood of spoofed IKE_SA_INIT requests ---------------------------
SECRET = rng.randbytes(32)

def responder(ni, ip, spii, cookie=None, cookies_on=False):
    expect = b'\x01' + hashlib.sha256(ni + ip.encode() + spii + SECRET).digest()
    if cookies_on and cookie != expect:
        return 'N(COOKIE)', expect, 0             # no state kept, no exponentiation
    return 'SAr1, KEr, Nr', None, 1                # state kept, one exponentiation

print()
print('A FLOOD OF 200 IKE_SA_INIT REQUESTS FROM SPOOFED ADDRESSES')
for on in (False, True):
    work = sum(responder(rng.randbytes(32), '198.18.%d.%d' % (n // 250, n % 250),
                         rng.randbytes(8), cookies_on=on)[2] for n in range(200))
    print('  cookies %-3s: %3d half-open IKE SAs kept, %3d Diffie-Hellman computations'
          % ('on' if on else 'off', work, work))
ni, spii = rng.randbytes(32), rng.randbytes(8)
reply, cookie, _ = responder(ni, '203.0.113.50', spii, cookies_on=True)
print('  a real initiator is first told      :', reply)
print('  it repeats the request with the cookie:',
      responder(ni, '203.0.113.50', spii, cookie=cookie, cookies_on=True)[0])
print('  a spoofer never receives the cookie, because it goes to the address it forged')
munotes.in506

IPsec Key Management: Oakley, ISAKMP and IKE

group 14: 2048-bit prime True, and (P - 1) / 2 prime True: a safe prime

IKE_SA_INIT: two messages, then both sides derive seven keys from SKEYSEED
  SK_d   32 bytes  same on both sides: True
  SK_ai  32 bytes  same on both sides: True
  SK_ar  32 bytes  same on both sides: True
  SK_ei  16 bytes  same on both sides: True
  SK_er  16 bytes  same on both sides: True
  SK_pi  32 bytes  same on both sides: True
  SK_pr  32 bytes  same on both sides: True
IKE_AUTH: the responder checks the initiator's AUTH: True

A MAN IN THE MIDDLE swaps in her own Diffie-Hellman values both ways
  the two sides now hold different keys : True
  and Mallory holds both sets of keys   : True
  the initiator's AUTH, relayed, checks : False
  so the responder refuses the IKE SA: AUTH covers the KE it received and the keys
  it derived, and without the pre-shared key Mallory cannot make a new one

A FLOOD OF 200 IKE_SA_INIT REQUESTS FROM SPOOFED ADDRESSES
  cookies off: 200 half-open IKE SAs kept, 200 Diffie-Hellman computations
  cookies on :   0 half-open IKE SAs kept,   0 Diffie-Hellman computations
  a real initiator is first told      : N(COOKIE)
  it repeats the request with the cookie: SAr1, KEr, Nr
  a spoofer never receives the cookie, because it goes to the address it forged
munotes.in507

IPsec Key Management: Oakley, ISAKMP and IKE

What the run establishes, in order.

The group is sound. A 2048-bit prime whose half, less one, is also prime.

Both sides derive the same seven keys, of the sizes the algorithms need, from nothing but the Diffie-Hellman secret, the nonces and the SPIs; and the responder's check of the initiator's AUTH succeeds.

The man in the middle gets everything except acceptance. By swapping in her own Diffie-Hellman values both ways, Mallory leaves the two sides with different keys and holds both sets herself, exactly the attack of Module 1. But the initiator's AUTH, relayed to the responder, fails, because it was computed over a first message containing the initiator's Diffie-Hellman value, not the one the responder received, and with keys the responder does not have. Without the pre-shared key Mallory cannot compute a replacement.

Cookies turn a flood into nothing. Two hundred requests from forged addresses cost the responder 200 exponentiations and 200 half-open SAs with cookies off; with cookies on, it keeps no state and does no exponentiation, because it answers each with a cookie that goes to the forged address. A real initiator, told to present a cookie, does so and is served.

munotes.in508

IPsec Key Management: Oakley, ISAKMP and IKE

Where things stand

  • IKEv1 is Historic. RFC 9395, April 2023: "IKEv1 has been deprecated, and RFCs 2407, 2408, and 2409 have been moved to Historic status", noting among IKEv2's improvements that IKEv1 is "vulnerable to amplification attacks". Systems still running IKEv1 "should be upgraded and reconfigured to run IKEv2".
  • Algorithms for IKEv2, RFC 8247 (September 2017): Diffie-Hellman group 14, 2048-bit MODP, MUST; group 19, the 256-bit elliptic curve group, SHOULD; the 1,536-bit and 1,024-bit groups SHOULD NOT; the 768-bit group MUST NOT; HMAC-SHA-256 as the pseudo-random function MUST; AES-CBC encryption MUST.
  • Curve25519 is defined for IKEv2 by RFC 8031 (December 2016) as group 31.

Distinctions that carry marks

OakleyISAKMPIKE
What it isa key determination protocola framework of formats and proceduresthe key exchange protocol IPsec uses
Providesauthenticated Diffie-Hellman, cookies, nonces, groups, perfect forward secrecyheaders, payloads, exchange types, two phasesIKEv1: Oakley inside ISAKMP; IKEv2: one protocol
DocumentRFC 2412 (1998)RFC 2408 (1998), now HistoricRFC 2409 (Historic); RFC 7296
IKEv1IKEv2
Messages to the first IPsec SAMain Mode 6 + Quick Mode 3, or Aggressive 3 + Quick 34
ModesMain, Aggressive, Quicknone: IKE_SA_INIT, IKE_AUTH, CREATE_CHILD_SA, INFORMATIONAL
Denial-of-service protectioncookies in every message's headercookies on demand, when under attack
Statusdeprecated, Historic (RFC 9395, 2023)current

What beginners get wrong here

Calling ISAKMP a key exchange. It defines how negotiations are carried, not how keys are agreed; Oakley, or IKE's own exchange, does that.

Saying cookies encrypt anything. They prove only that the initiator receives messages at its address, and they must be cheap.

Thinking Diffie-Hellman alone is safe. Unauthenticated, it falls to a man in the middle; IKE's AUTH is what binds the exchange to the parties.

Describing IKEv1's Main Mode as current practice. IKEv1 is Historic; IKEv2 is what is deployed.

Forgetting phase 2. The expensive, authenticated phase 1 sets up a protected channel once; the cheap phase 2 creates the SAs that actually protect traffic.

Quick revision

  • Manual (by hand, small fixed setups) against automated (IKE) key management; RFC 4301 requires both be possible; RFC 8504: manual "not recommended".
  • Threats to plain Diffie-Hellman: no authentication (man in the middle), clogging, replay.
  • Oakley (RFC 2412): cookies, negotiated algorithms, authentication bound to the exponentials, chosen groups, nonces, perfect forward secrecy; authentication by pre-shared keys (required), signatures, public-key encryption.
  • Cookies: depend on the parties, need the issuer's secret, fast to compute (RFC 2408, after Karn).
  • ISAKMP (RFC 2408): a framework; header with two 8-byte cookies; payloads; exchange types; two phases.
  • IKEv1 (RFC 2409): Main Mode 6, Aggressive Mode 3, Quick Mode 3. Historic since RFC 9395 (2023).
  • IKEv2 (RFC 7296): IKE_SA_INIT (SA, KE, N) and IKE_AUTH (ID, AUTH, first Child SA, traffic selectors); CREATE_CHILD_SA; INFORMATIONAL. SKEYSEED = prf(Ni | Nr, g^ir); seven keys by prf+; AUTH defeats the man in the middle.
  • RFC 8247: group 14 MUST, group 19 SHOULD, groups 2 and 5 SHOULD NOT, HMAC-SHA-256 PRF MUST.
munotes.in509

IPsec Key Management: Oakley, ISAKMP and IKE

Test yourself

1. Distinguish manual from automated key management in IPsec. In manual key management an administrator configures each system with its own keys and the keys of the systems it talks to; it suits small, fixed arrangements, and keys change only when someone retypes them. Automated key management uses a protocol, IKE, to negotiate security associations and derive fresh keys on demand, so it scales to many systems and supports regular rekeying; RFC 8504 describes manual keying as of limited applicability and not recommended.

2. What features does Oakley add to Diffie-Hellman? Cookies to resist clogging attacks; negotiation of the encryption, key derivation and authentication methods; authentication that binds the Diffie-Hellman exponentials to the identities of the parties, defeating a man in the middle; the choice of standard or user-defined groups; nonces against replay; and perfect forward secrecy. Its authentication may use pre-shared keys, digital signatures or public-key encryption.

3. State the requirements for ISAKMP cookies and explain how a cookie defeats clogging. A cookie must depend on the specific parties, must be something only the issuing entity can generate, using a local secret that cannot be deduced from any cookie, and must be fast to compute. The responder answers a first request with a cookie instead of doing a Diffie-Hellman computation, and does the expensive work only when the request is repeated with the cookie. A forger using another party's address never receives the cookie, so it can never make the responder do the work.

4. What is ISAKMP, and how does it differ from Oakley? ISAKMP is a framework that defines the message formats, payloads, exchange types and procedures for establishing, negotiating, modifying and deleting security associations, independently of any particular key exchange. Oakley is a key determination protocol, based on authenticated Diffie-Hellman, that actually produces the keys. IKEv1 combined them, Oakley's exchange carried in ISAKMP's messages.

5. Describe the IKEv2 initial exchanges. In IKE_SA_INIT the initiator sends its proposed algorithms, its Diffie-Hellman value and a nonce, and the responder replies with its chosen algorithms, its own Diffie-Hellman value and nonce, and optionally a certificate request; both can then compute SKEYSEED and the keys derived from it. In IKE_AUTH, encrypted and integrity-protected with those keys, each side sends its identity, optionally certificates, and an AUTH value that proves its identity and covers the earlier messages, and the pair sets up the first Child SA with its traffic selectors.

munotes.in510

IPsec Key Management: Oakley, ISAKMP and IKE

6. How does IKEv2's AUTH payload defeat a man in the middle? The AUTH value is computed over the sender's own first message, which contains its Diffie-Hellman value, over the other side's nonce, and over a value derived from the keys, using a secret the attacker lacks, a pre-shared key or a private key. A man in the middle who substituted his own Diffie-Hellman values leaves the responder checking AUTH against a first message and keys different from those the initiator used, so the check fails, and without the secret he cannot compute a valid replacement.

7. What is the status of IKEv1 today? It is deprecated. RFC 9395, of April 2023, moved RFCs 2407, 2408 and 2409, the documents defining IKEv1 and ISAKMP, to Historic status, stating that IKEv2, first published in December 2005 and now RFC 7296, is a full replacement, and that systems running IKEv1 should be upgraded to IKEv2.

Contents This chapter on its own page

munotes.in511

Chapter Seventy-Seven

Web Security Considerations

Syllabus topic Module 2, "Web Security: Web Security Considerations"

In one line

Web security is about protecting three things, the server, the browser and the traffic between them, against four kinds of harm: altering what is sent, reading it, stopping it, and pretending to be someone else.

In the words an answer should use: the World Wide Web is a client/server application over the Internet and TCP/IP intranets, and web security considers threats to integrity, confidentiality, availability (denial of service) and authentication, arising at the web server, at the web browser and in the network traffic between them; countermeasures can be placed at the network level (IPsec), the transport level (SSL/TLS) or the application level (for example S/MIME, PGP or SET).

Why the web needs its own security thinking

Earlier chapters protected email, which is sent and read later, and IP packets, which no user sees. The web is different in ways that make it harder.

It is two-way. A browser does not only fetch; it submits forms, uploads files and runs scripts. A web server therefore accepts input from anyone on the Internet, and every input is a way in.

The server is a door to other systems. A college's results page is the front of a database of every student's marks. Breaking the web server can mean reaching everything behind it.

The software is large and complicated. Browsers and web servers are among the largest programs most people run, and large programs contain faults that nobody has found yet.

The people using it are not security experts. A student entering a password on a convincing copy of the college portal is doing nothing unusual, which is exactly why the copy works.

The four kinds of threat

The threats are classified by what they harm.

ThreatExamplesConsequencesCountermeasures
Integrityaltering data on a server, altering a page or a form submission in transit, a browser add-on that changes what the user seeswrong information believed, a compromised machine, every other attack made easiercryptographic checksums: MACs and signatures on what is sent
Confidentialityeavesdropping on the network, stealing data from a server or a browser, learning which user talks to which serverloss of information, loss of privacyencryption; web proxies to hide who is talking
Denial of serviceflooding a server with requests, exhausting its memory or connections, cutting it off by attacking its name servicethe service is unusable when it is neededdifficult to prevent: capacity, filtering, the DDoS chapters
Authenticationimpersonating a legitimate user, impersonating a legitimate site, forged datafalse identities believed, false information acceptedcryptographic techniques: certificates, signatures, authenticated protocols

Passive and active. The same threats divide the way the OSI chapter divided attacks. Passive attacks read traffic or learn which sites a user visits. Active attacks change traffic, impersonate a party, or deny service.

munotes.in512

Web Security Considerations

Three places to attack. An attacker can go after the server, the browser, or the traffic between them. The protocols in the next chapters protect the third; the first two need secure software, patching, and the firewall and intrusion-detection chapters at the end of this module.

Where security can be placed

The protection of web traffic can sit at three levels of the stack, and a question on web security considerations expects all three with their trade-offs.

LevelHowProtectsAdvantageLimitation
Network, IPIPsecevery packet between two pointstransparent to applications and users; protects everythingprotects to the edge of a network, not to a particular application; needs configuration on both sides
Transport, above TCPSSL/TLSone connection, end to end between browser and serverneeds nothing from the network; built into every browser and web serverthe application must use it; addresses and the server name remain visible
Applicationsecurity inside the application: S/MIME, PGP, SET, Kerberosexactly the data the application choosescan be tailored to the application's needsmust be built into every application separately

TLS won the web because it sits in the one place both ends control completely: the browser and the server. Neither the user nor the network operator has to do anything.

The run: what an observer can see

The listing first parses the beginning of a real ClientHello, the first message a browser sends when it opens an HTTPS connection to munotes.in, captured on 30 September 2026. Then it builds the same web request, a student fetching a result with a session cookie, as the bytes each arrangement would put on the wire, and searches those bytes for each piece of information. Finally an on-path device injects a script into a page.

01 00 05 b3 03 03 2c 82 2b bc 28 1d 17 7e be 9b
4c a0 a2 91 61 df 7e be 60 6d e3 82 dc 54 8b a6
a1 b7 dc 6d a9 60 20 46 e3 1c b7 1d f6 2d 32 bf
8c 77 8d 3d 23 4c 37 95 19 1a e7 18 91 80 27 0e
3e 31 eb 42 d3 f5 2f 00 06 13 02 13 03 13 01 01
00 05 64 00 00 00 0f 00 0d 00 00 0a 6d 75 6e 6f
74 65 73 2e 69 6e 00 0b 00 02 01 00 00 0a 00 12
# What each place for security hides from someone on the path, and what tampering does.
import hashlib, hmac

# ---- the first 112 bytes of a real TLS 1.3 ClientHello sent to munotes.in -----------------
data = bytes.fromhex(open('clienthello-start.hex').read())
kind, length = data[0], int.from_bytes(data[1:4], 'big')
at = 4 + 2 + 32                                        # handshake header, version, random
at += 1 + data[at]                                     # session id
suites = data[at + 2:at + 2 + int.from_bytes(data[at:at + 2], 'big')]
at += 2 + len(suites)
at += 1 + data[at]                                     # compression methods
at += 2                                                # length of all extensions
ext_type = int.from_bytes(data[at:at + 2], 'big')
name_len = int.from_bytes(data[at + 7:at + 9], 'big')
server_name = data[at + 9:at + 9 + name_len].decode()
NAMES = {0x1301: 'TLS_AES_128_GCM_SHA256', 0x1302: 'TLS_AES_256_GCM_SHA384',
         0x1303: 'TLS_CHACHA20_POLY1305_SHA256'}
print('A REAL CLIENTHELLO, SENT BEFORE ANY KEY EXISTS, SO READABLE BY ANYONE ON THE PATH')
print('  handshake type %d (ClientHello), %d bytes in all' % (kind, length + 4))
print('  cipher suites offered :', ', '.join(NAMES[int.from_bytes(suites[i:i + 2], 'big')]
                                         for i in range(0, len(suites), 2)))
print('  first extension, type %d (server_name): %r' % (ext_type, server_name))

# ---- one web request, built as the bytes each arrangement actually puts on the wire -----
def seal(key, data):                      # a stand-in cipher: what matters is what stays clear
    stream = b''.join(hashlib.sha256(key + i.to_bytes(4, 'big')).digest()
                      for i in range(len(data) // 32 + 1))
    return bytes(a ^ b for a, b in zip(data, stream))

def ip(a):
    return bytes(map(int, a.split('.')))

CLIENT, SERVER, HOST = '10.1.4.7', '203.0.113.80', 'results.college.example'
PATH, COOKIE, BODY = '/sem5/roll/101', 'session=7f3a9c', 'roll=101&dob=2006-04-11'
http = ('POST %s HTTP/1.1\r\nHost: %s\r\nCookie: %s\r\n\r\n%s'
        % (PATH, HOST, COOKIE, BODY)).encode()
k = b'k' * 32

def packet(src, dst, port, payload):
    return ip(src) + ip(dst) + port.to_bytes(2, 'big') + payload

WIRE = {
    'plain HTTP': packet(CLIENT, SERVER, 80, http),
    'application level': packet(CLIENT, SERVER, 80,
                                http.replace(BODY.encode(), seal(k, BODY.encode()))),
    'transport level, TLS': packet(CLIENT, SERVER, 443, b'\x16\x03\x01' + HOST.encode())
                            + packet(CLIENT, SERVER, 443, b'\x17\x03\x03' + seal(k, http)),
    'network level, IPsec VPN': ip('198.51.100.1') + ip('198.51.100.9') + b'\x32' +
                                seal(k, packet(CLIENT, SERVER, 443, http)),
}
FIELDS = {'client': ip(CLIENT), 'server': ip(SERVER), 'hostname': HOST.encode(),
          'path': PATH.encode(), 'cookie': COOKIE.encode(), 'body': BODY.encode()}
print()
print('WHAT AN OBSERVER ON THE PATH CAN READ, found by searching the bytes on the wire')
print('  %-26s %s' % ('', '  '.join('%-8s' % f for f in FIELDS)))
for where, wire in WIRE.items():
    print('  %-26s %s' % (where, '  '.join('%-8s' % ('yes' if v in wire else '-')
                                           for v in FIELDS.values())))

# ---- integrity: an on-path device injects a line into a response ------------------------
key = hashlib.sha256(b'a session key agreed by the handshake').digest()
page = b'<p>Result: PASS</p>'
tag = hmac.new(key, page, hashlib.sha256).digest()
injected = page + b'<script src="//ads.example/x.js"></script>'
print()
print('AN ON-PATH DEVICE ADDS A SCRIPT TO THE PAGE ON ITS WAY')
print('  plain HTTP: the browser shows the altered page, with no way to know')
print('  TLS record: its integrity check verifies           :',
      hmac.compare_digest(tag, hmac.new(key, injected, hashlib.sha256).digest()))
print('              so the browser rejects it and the connection fails, loudly')
munotes.in513

Web Security Considerations

A REAL CLIENTHELLO, SENT BEFORE ANY KEY EXISTS, SO READABLE BY ANYONE ON THE PATH
  handshake type 1 (ClientHello), 1463 bytes in all
  cipher suites offered : TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256
  first extension, type 0 (server_name): 'munotes.in'

WHAT AN OBSERVER ON THE PATH CAN READ, found by searching the bytes on the wire
                             client    server    hostname  path      cookie    body
  plain HTTP                 yes       yes       yes       yes       yes       yes
  application level          yes       yes       yes       yes       yes       -
  transport level, TLS       yes       yes       yes       -         -         -
  network level, IPsec VPN   -         -         -         -         -         -

AN ON-PATH DEVICE ADDS A SCRIPT TO THE PAGE ON ITS WAY
  plain HTTP: the browser shows the altered page, with no way to know
  TLS record: its integrity check verifies           : False
              so the browser rejects it and the connection fails, loudly
munotes.in514

Web Security Considerations

What the run establishes, in order.

Some of HTTPS happens in the clear. The ClientHello is sent before any key exists, so anyone on the path can read it. It offers three cipher suites and, in its very first extension, names the site: munotes.in. TLS 1.3 encrypts everything after the server's reply, RFC 8446 says, but the server name the browser asks for is still readable here.

Each level hides a different amount. With plain HTTP everything is readable, the cookie that is the student's login included. Encrypting only the form body at the application level still leaves the path and the cookie exposed. TLS hides the path, the cookie and the body, but not the addresses or the host name. An IPsec tunnel between two gateways hides all of it, even which hosts are talking, from an observer between the gateways.

Integrity is what stops injection. Over plain HTTP a device on the path can add a script to a page and the browser has no way to know. Over TLS the record's integrity check fails, and the browser rejects it, loudly.

What has changed

Encryption is now the expectation, not the exception. In May 2014 the IETF agreed, in RFC 7258, that "Pervasive monitoring is a technical attack that should be mitigated in the design of IETF protocols", and its protocols since, TLS 1.3 among them, are designed to encrypt as much as they can rather than only what is obviously sensitive, such as a payment page.

TLS 1.3 encrypts more of the handshake. RFC 8446 (August 2018), and RFC 9846, which replaced it in July 2026: "All handshake messages after the ServerHello are now encrypted", including the server's certificate, which earlier versions sent in the clear.

munotes.in515

Web Security Considerations

The server name remains visible, as the run shows, which is why a site's name, though not its pages, is still known to anyone on the path.

Distinctions that carry marks

Passive attackActive attack
Doesreads or observesalters, injects, impersonates or blocks
Web exampleseavesdropping, learning which sites a user visitsaltering a page, a fake site, flooding a server
Main defenceencryptionintegrity checks, authentication, capacity
IPsecTLSApplication level
Who must actnetwork administrators, both endsthe browser and the web servereach application's designer
Hides the host nameyes, behind the gatewaysnono
Hides the path and cookieyesyesonly if the application encrypts them
Typical web usea VPN into a campus networkevery HTTPS sitesigned or encrypted documents, payment schemes

What beginners get wrong here

Saying HTTPS hides which site a user visits. It hides the pages and the data, not the addresses, and the server name travels in the ClientHello, as the run shows.

Treating denial of service as solvable by cryptography. Encryption and signatures protect integrity, confidentiality and authentication; denial of service needs capacity and filtering.

Thinking web security is only about the traffic. Servers and browsers are attacked directly, and TLS does nothing about a vulnerable server or a malicious browser add-on.

Putting TLS in the network layer. It sits above TCP, at the transport level.

Quick revision

  • Web security protects the server, the browser and the traffic between them.
  • Why hard: two-way, the server is a door to internal systems, complex software, untrained users.
  • Threats: integrity (checksums), confidentiality (encryption, proxies), denial of service (hard to prevent), authentication (cryptographic techniques).
  • Passive (read) against active (alter, impersonate, block).
  • Levels: network (IPsec, transparent, everything between two points), transport (SSL/TLS, per connection, both ends only), application (S/MIME, PGP, SET, Kerberos, tailored).
  • RFC 7258 (2014): pervasive monitoring is an attack. TLS 1.3 encrypts everything after the ServerHello; the server name in the ClientHello is still visible.

Test yourself

1. Why does the web pose security problems that email or file transfer do not? Because it is two-way, so web servers accept input from anyone and can be attacked through it; because a web server is often the front door to an organisation's databases and internal systems; because browsers and servers are large, complex programs likely to contain undiscovered faults; and because its users are ordinary people who cannot be expected to recognise attacks such as a copied site.

2. Classify the threats to web security, with a consequence and a countermeasure for each. Integrity threats, such as modification of data on a server or of messages in transit, lead to false information being believed or machines being compromised, and are countered by cryptographic checksums. Confidentiality threats, such as eavesdropping or theft of data from servers and browsers, lead to loss of information and privacy, and are countered by encryption and web proxies. Denial-of-service threats, such as flooding a server with bogus requests, make the service unusable and are difficult to prevent. Authentication threats, such as impersonation of users or sites and forged data, lead to false identities being accepted and are countered by cryptographic techniques.

munotes.in516

Web Security Considerations

3. Compare the three levels at which web traffic can be secured. At the network level IPsec protects all IP traffic between two points, transparently to applications and users, but only as far as the points it is configured between. At the transport level SSL/TLS protects one connection between browser and server, needs nothing from the network and is built into every browser and server, but leaves the addresses and server name visible. At the application level security is built into a particular application, such as S/MIME, PGP or SET, which lets it be tailored to that application but requires every application to do it separately.

4. Which information remains visible to an observer on the path when a student uses HTTPS? The source and destination IP addresses, the port, the sizes and timing of the traffic, and the name of the site, which the browser sends in the clear in the ClientHello. The page path, the cookies, the form data and the content of the pages are encrypted.

5. How does TLS prevent an on-path device from injecting content into a page? Every TLS record carries an integrity check computed with a key only the browser and the server share, so any alteration, such as an injected script, makes the check fail and the browser rejects the record and closes the connection, instead of silently displaying the altered page as it would over plain HTTP.

Contents This chapter on its own page

munotes.in517

Chapter Seventy-Eight

SSL: The Architecture and the Record Protocol

Syllabus topic Module 2, "Web Security: Secure Socket Layer and Transport Layer Security"

In one line

SSL is a layer between an application such as a web browser and TCP: at the bottom, a record protocol that chops data into pieces, checks and encrypts each one; above it, three small protocols that agree the keys, switch them on, and report problems.

In the words an answer should use: the Secure Sockets Layer (SSL), and its successor Transport Layer Security (TLS), provide secure communication over TCP. The SSL Record Protocol provides confidentiality and message integrity for higher-layer data; above it run three SSL-specific protocols, the Handshake Protocol, the Change Cipher Spec Protocol and the Alert Protocol, which set up and manage the security of a connection, alongside the application's own protocol, such as HTTP.

Where SSL sits

SSL was designed at Netscape for the web. Version 3.0 became the basis of the IETF's Transport Layer Security, and the names are used together because SSL's design is still TLS's design, although no SSL version is in use today. SSL 3.0 is documented as a historical record in RFC 6101 (August 2011) and prohibited by RFC 7568 (June 2015); the TLS versions chapter tells that story.

LayerWhat runs there
ApplicationHTTP, and SSL's own Handshake, Change Cipher Spec and Alert protocols
SSLthe SSL Record Protocol
TransportTCP
NetworkIP

SSL sits above TCP, which gives it a reliable byte stream to work on, and below the application, which hands it data to protect. An application must be written to use it, which is why a web address begins https: HTTP over SSL/TLS.

Session and connection

SSL separates two ideas, and a question on SSL's architecture almost always asks for both.

A connection is a transport relationship between a client and a server, a single TCP connection carrying SSL. Connections are short-lived.

A session is an association between a client and a server, created by the Handshake Protocol, which defines a set of security parameters that several connections can share. Sessions exist so that the expensive handshake need not be repeated for every connection; a browser that opens several connections to one site can resume one session for all of them.

RFC 6101 s.5.1 lists what each holds.

Session stateWhat it is
Session identifierchosen by the server, to identify an active or resumable session
Peer certificatethe X.509 certificate of the other side; may be empty
Compression methodthe algorithm used to compress data before encryption
Cipher specthe bulk encryption algorithm, the MAC algorithm, and their attributes
Master secreta 48-byte secret shared by client and server
Is resumablewhether the session may start new connections
Connection stateWhat it is
Server and client randombyte strings chosen by each side, fresh for every connection
Server write MAC secretthe key for MACs on data the server sends
Client write MAC secretthe key for MACs on data the client sends
Server write keythe encryption key for data the server sends
Client write keythe encryption key for data the client sends
Initialisation vectorsfor a CBC cipher, one IV per key
Sequence numbersone count for each direction, set to zero at each change cipher spec
munotes.in518

SSL: The Architecture and the Record Protocol

Notice the pairs. Each direction has its own MAC key and its own encryption key. A message the server sends cannot be reflected back to it as if the client had sent it, because it would be checked with the wrong key.

The Record Protocol

The Record Protocol gives two services: confidentiality, by encryption with the write key, and message integrity, by a MAC with the write MAC secret. RFC 6101 describes its work in one sentence: SSL "fragments the data into manageable blocks, optionally compresses the data, applies a MAC, encrypts, and transmits the result". Received data goes back the other way: decrypted, verified, decompressed and reassembled.

The five steps, in order:

  1. Fragmentation. The application's data is cut into fragments of at most 2^14 = 16,384 bytes.
  2. Compression, optional. It must be lossless and must not make the content more than 1,024 bytes longer.
  3. MAC. A MAC is computed over the compressed fragment together with the sequence number, the content type and the length, and appended.
  4. Encryption. The fragment and MAC are encrypted. For a block cipher, padding brings them to a whole number of blocks; the encrypted result must not exceed 2^14 + 2,048 bytes.
  5. Header. A five-byte header is put in front: the content type, the major and minor version, and the length of what follows.

The content types say which protocol a record carries: 20 change cipher spec, 21 alert, 22 handshake, 23 application data.

SSL 3.0's MAC. RFC 6101 defines it as a nested hash with two fixed pads, not the HMAC of the Module 1 chapter:

hash(MAC_write_secret + pad_2 +
     hash(MAC_write_secret + pad_1 + seq_num + type + length + fragment))

pad_1 = the byte 0x36 repeated 48 times for MD5, 40 times for SHA-1
pad_2 = the byte 0x5C repeated 48 times for MD5, 40 times for SHA-1

TLS replaced it with standard HMAC, and added the protocol version to what the MAC covers.

The run: a message through the record layer, and four attacks

The listing sends a 40,000-byte message through a TLS 1.2 record layer using AES-128 in CBC mode and HMAC-SHA256, and prints what each record costs. Then it attacks the records on their way. The cipher is a stand-in whose sizes are exactly AES-CBC's; the MACs are the real ones.

munotes.in519

SSL: The Architecture and the Record Protocol

# The SSL/TLS record protocol: fragment, MAC, pad, encrypt, add a header; then attack it.
import hashlib, hmac, random

rng = random.Random(780)
MAC_KEY, ENC_KEY = rng.randbytes(32), rng.randbytes(16)
APPLICATION_DATA, HANDSHAKE = 23, 22

def ssl3_mac(secret, seq, ctype, fragment):        # RFC 6101 s.5.2.3.1, with SHA-1
    inner = hashlib.sha1(secret + b'\x36' * 40 + seq.to_bytes(8, 'big') + bytes([ctype]) +
                         len(fragment).to_bytes(2, 'big') + fragment).digest()
    return hashlib.sha1(secret + b'\x5c' * 40 + inner).digest()

def tls12_mac(key, seq, ctype, fragment):          # RFC 5246 s.6.2.3.1, HMAC-SHA256
    header = seq.to_bytes(8, 'big') + bytes([ctype]) + b'\x03\x03' + \
        len(fragment).to_bytes(2, 'big')
    return hmac.new(key, header + fragment, hashlib.sha256).digest()

def keystream(iv, n):                               # a stand-in for AES-128-CBC's output
    return b''.join(hashlib.sha256(ENC_KEY + iv + i.to_bytes(4, 'big')).digest()
                    for i in range(n // 32 + 1))[:n]

def protect(seq, ctype, fragment):                  # TLS 1.2, AES-128-CBC, HMAC-SHA256
    mac = tls12_mac(MAC_KEY, seq, ctype, fragment)
    pad = 15 - (len(fragment) + len(mac)) % 16       # padding_length byte makes it whole
    plain = fragment + mac + bytes([pad]) * (pad + 1)
    iv = rng.randbytes(16)
    body = iv + bytes(a ^ b for a, b in zip(plain, keystream(iv, len(plain))))
    return bytes([ctype]) + b'\x03\x03' + len(body).to_bytes(2, 'big') + body

def unprotect(expected_seq, record):
    ctype, body = record[0], record[5:]
    iv, data = body[:16], body[16:]
    plain = bytes(a ^ b for a, b in zip(data, keystream(iv, len(data))))
    pad = plain[-1]
    fragment, mac = plain[:-(pad + 1) - 32], plain[-(pad + 1) - 32:-(pad + 1)]
    ok = hmac.compare_digest(mac, tls12_mac(MAC_KEY, expected_seq, ctype, fragment))
    return fragment if ok else None

# ---- 1. one 40,000-byte message through the record layer ----------------------------------
message = bytes(rng.getrandbits(8) for _ in range(40000))
fragments = [message[i:i + 2 ** 14] for i in range(0, len(message), 2 ** 14)]
records = [protect(seq, APPLICATION_DATA, f) for seq, f in enumerate(fragments)]
print('A 40,000-BYTE MESSAGE, FRAGMENTED AT 2^14 = 16,384 BYTES')
for seq, (f, r) in enumerate(zip(fragments, records)):
    print('  record %d: fragment %5d + MAC 32 + padding %2d + IV 16 + header 5 = %5d bytes'
          % (seq, len(f), len(r) - 5 - 16 - len(f) - 32, len(r)))
total = sum(len(r) for r in records)
print('  %d bytes on the wire for 40,000 of data: %d bytes of overhead, %.2f per cent'
      % (total, total - 40000, 100 * (total - 40000) / 40000))
print('  every record within RFC 6101 s.5.2.3\'s limit of 2^14 + 2048:',
      all(len(r) - 5 <= 2 ** 14 + 2048 for r in records))

# ---- 2. the receiver, in order, and three attacks ------------------------------------------
print()
print('THE RECEIVER CHECKS EACH MAC WITH THE SEQUENCE NUMBER IT EXPECTS NEXT')
print('  in order                      :',
      [unprotect(s, r) is not None for s, r in enumerate(records)])
print('  records 1 and 2 swapped       :', [unprotect(s, r) is not None
                                             for s, r in enumerate([records[0], records[2],
                                                                    records[1]])])
print('  record 0 replayed as the 4th  :', unprotect(3, records[0]) is not None)
relabelled = bytes([HANDSHAKE]) + records[1][1:]
print('  record 1 relabelled handshake :', unprotect(1, relabelled) is not None)
tampered = bytearray(records[2])
tampered[100] ^= 1
print('  one bit of record 2 changed   :', unprotect(2, bytes(tampered)) is not None)

# ---- 3. SSL 3.0's MAC against TLS's HMAC -----------------------------------------------------
f = b'GET /results HTTP/1.1\r\n'
print()
print('SSL 3.0 MAC (RFC 6101) AGAINST TLS 1.2 MAC (RFC 5246), same record')
print('  SSL 3.0: SHA-1, pads 0x36 and 0x5C of 40 bytes, not HMAC; covers the version: no')
print('    %s' % ssl3_mac(MAC_KEY[:20], 0, APPLICATION_DATA, f).hex())
print('  TLS 1.2: HMAC-SHA256; covers sequence number, type, version and length')
print('    %s' % tls12_mac(MAC_KEY, 0, APPLICATION_DATA, f).hex())
munotes.in520

SSL: The Architecture and the Record Protocol

A 40,000-BYTE MESSAGE, FRAGMENTED AT 2^14 = 16,384 BYTES
  record 0: fragment 16384 + MAC 32 + padding 16 + IV 16 + header 5 = 16453 bytes
  record 1: fragment 16384 + MAC 32 + padding 16 + IV 16 + header 5 = 16453 bytes
  record 2: fragment  7232 + MAC 32 + padding 16 + IV 16 + header 5 =  7301 bytes
  40207 bytes on the wire for 40,000 of data: 207 bytes of overhead, 0.52 per cent
  every record within RFC 6101 s.5.2.3's limit of 2^14 + 2048: True

THE RECEIVER CHECKS EACH MAC WITH THE SEQUENCE NUMBER IT EXPECTS NEXT
  in order                      : [True, True, True]
  records 1 and 2 swapped       : [True, False, False]
  record 0 replayed as the 4th  : False
  record 1 relabelled handshake : False
  one bit of record 2 changed   : False

SSL 3.0 MAC (RFC 6101) AGAINST TLS 1.2 MAC (RFC 5246), same record
  SSL 3.0: SHA-1, pads 0x36 and 0x5C of 40 bytes, not HMAC; covers the version: no
    b6a8f27988bf2da9d3a81c06522016e113564475
  TLS 1.2: HMAC-SHA256; covers sequence number, type, version and length
    90d28795ac6015163ec110a2af5867631a8395780bbd7ba9a25b046605c9a8f9

What the run establishes, in order.

Fragmentation is at 2^14. The 40,000 bytes become records carrying 16,384, 16,384 and 7,232 bytes.

Each record costs 69 bytes more than its data. A 32-byte MAC, 16 bytes of padding counting its length byte, a 16-byte IV and the 5-byte header. For this message that is 207 bytes, about half of one per cent. Every record is within SSL's limit.

The sequence number inside the MAC stops reordering and replay. The sequence number is never sent; each side counts. So when records 1 and 2 are swapped, the receiver checks record 2 with sequence number 1 and the MAC fails, and the record after it fails too. A replayed record 0, arriving where the receiver expects number 3, fails for the same reason.

munotes.in521

SSL: The Architecture and the Record Protocol

The content type inside the MAC stops relabelling. Changing a record's type from application data to handshake, in the header outside the encryption, makes its MAC fail, so an attacker cannot make data be taken for a handshake message.

Any change to the contents fails. One flipped bit, and the record is rejected.

SSL 3.0's MAC and TLS's HMAC give different values for the same record, because they are different constructions over different inputs.

What went wrong, and what TLS 1.3 changed

The record layer's design was sound in outline; several details were not. RFC 7457 (February 2015) catalogues the attacks.

  • Compression leaked secrets. The CRIME attack "allows an active attacker to decrypt ciphertext (specifically, cookies) when TLS is used with TLS-level compression": how much a record compressed revealed what was in it. TLS 1.3 removed compression altogether.
  • Predictable IVs. The BEAST attack used "the predictable initialization vector" of TLS 1.0's CBC mode to recover cookies. TLS 1.1 (RFC 4346, April 2006) replaced "the implicit Initialization Vector (IV)" with "an explicit IV to protect against CBC attacks", which the run's records carry.
  • MAC-then-encrypt. SSL and TLS up to 1.2 compute the MAC and then encrypt, so the receiver must decrypt and check the padding before it can check the MAC. That order makes padding oracle attacks possible: the Lucky Thirteen attack, which uses timing, and the POODLE attack on SSL 3.0, for which RFC 7457 says there is "no known mitigation". The fixes are authenticated encryption such as AES-GCM, or encrypt-then-MAC.

TLS 1.3 keeps only authenticated encryption (AES-GCM, ChaCha20-Poly1305 and AES-CCM), so there is no separate MAC to order and no CBC padding to attack.

Distinctions that carry marks

SessionConnection
What it isan association, with shared security parametersone transport relationship, a TCP connection
Created bythe Handshake ProtocolTCP, then keyed from the session
Sharedby several connectionsnot shared
Holdssession ID, peer certificate, compression, cipher spec, master secret, resumable flagthe two randoms, four keys (a MAC key and an encryption key each way), IVs, sequence numbers
Lifetimelonger; can be resumedone connection
SSL 3.0 MACTLS HMAC
Constructionnested hash with fixed pads 0x36 and 0x5Cstandard HMAC
Coverssequence number, type, length, datathe same, plus the version

What beginners get wrong here

Saying the sequence number is sent in each record. It is counted separately by each side and included only in the MAC computation.

Swapping session and connection. Many connections share one session, not the other way round.

Listing the record steps in the wrong order. Fragment, compress, MAC, encrypt, add the header: the MAC is on the compressed data, and the header is added last and not encrypted.

munotes.in522

SSL: The Architecture and the Record Protocol

Thinking one key protects both directions. There are separate write keys and separate MAC secrets for the client and the server.

Describing compression as a feature to use. It enabled the CRIME attack and TLS 1.3 removed it.

Quick revision

  • SSL/TLS: above TCP, below the application. Record Protocol at the bottom; Handshake, Change Cipher Spec, Alert above it.
  • Session: shared by connections; session ID, peer certificate, compression method, cipher spec, 48-byte master secret, is resumable.
  • Connection: server and client random, server and client write MAC secrets, server and client write keys, IVs, sequence numbers.
  • Record steps: fragment (2^14), compress (at most +1,024), MAC, encrypt (at most 2^14 + 2,048), header (type, major and minor version, length).
  • Content types: 20 change cipher spec, 21 alert, 22 handshake, 23 application data.
  • SSL 3.0 MAC: nested hash with pads 0x36 and 0x5C; TLS: HMAC, and covers the version.
  • The sequence number and type inside the MAC stop reordering, replay and relabelling.
  • Attacks (RFC 7457): CRIME (compression), BEAST (predictable IV), padding oracles: Lucky Thirteen, POODLE. TLS 1.3: no compression, authenticated encryption only.

Test yourself

1. Name the protocols that make up SSL and state where each sits. The SSL Record Protocol sits directly above TCP and provides confidentiality and integrity for everything above it. Above the Record Protocol run three SSL-specific protocols, the Handshake Protocol, the Change Cipher Spec Protocol and the Alert Protocol, together with the application's own protocol, such as HTTP.

2. Distinguish an SSL session from an SSL connection, and list the state each holds. A session is an association between a client and a server, created by the Handshake Protocol, whose security parameters can be shared by several connections; its state is the session identifier, the peer certificate, the compression method, the cipher spec, the 48-byte master secret and a flag saying whether it can be resumed. A connection is a single transport relationship; its state is the server and client random values, the server and client write MAC secrets, the server and client write keys, the initialisation vectors and a sequence number for each direction.

3. Describe the operation of the SSL Record Protocol. The application's data is fragmented into blocks of at most 2^14 bytes; each fragment is optionally compressed, losslessly and without growing by more than 1,024 bytes; a MAC is computed over the compressed fragment together with the sequence number, content type and length; the fragment and MAC are encrypted, with padding for a block cipher, to at most 2^14 + 2,048 bytes; and a header giving the content type, the version and the length is prepended. The receiver reverses the steps.

munotes.in523

SSL: The Architecture and the Record Protocol

4. How does including the sequence number in the MAC protect the record layer? Each side keeps its own count of records sent and received, starting from zero at the change cipher spec, and includes it in the MAC without sending it. A record that is reordered, replayed, deleted or inserted arrives when the receiver expects a different number, so its MAC is checked against the wrong sequence number and fails.

5. Calculate the size on the wire of a 7,232-byte fragment sent with AES-128-CBC and HMAC-SHA256 in TLS 1.2. The fragment and its 32-byte MAC make 7,264 bytes, already a multiple of the 16-byte block, so a full block of 16 bytes of padding is added, counting the padding-length byte; with a 16-byte explicit IV the encrypted part is 7,296 bytes, and the 5-byte header brings the record to 7,301 bytes.

6. Why did TLS 1.3 remove compression and CBC with MAC-then-encrypt? Compression let the CRIME attack recover secrets such as cookies from how much records compressed. MAC-then-encrypt with CBC requires the receiver to handle padding before checking the MAC, which made padding oracle attacks such as Lucky Thirteen and POODLE possible. TLS 1.3 therefore has no compression and uses only authenticated encryption algorithms, which encrypt and authenticate in one operation.

Contents This chapter on its own page

munotes.in524

Chapter Seventy-Nine

The SSL and TLS Handshake

Syllabus topic Module 2, "Web Security: Secure Socket Layer and Transport Layer Security"

In one line

The handshake is the conversation at the start of every TLS connection in which the browser and the server agree which algorithms to use, the server proves who it is, the two agree a secret without anyone else learning it, and each checks that nobody tampered with the conversation.

In the words an answer should use: the SSL/TLS Handshake Protocol allows the server and client to authenticate each other, to negotiate an encryption algorithm, a MAC algorithm and cryptographic keys, and to establish the shared master secret from which the keys protecting the connection's records are derived; it runs before any application data is sent and consists of messages exchanged in four phases.

The handshake message

Every handshake message has three fields: a one-byte type, a three-byte length, and the content, whose parameters depend on the type.

TypeMessageParameters
0hello_requestnone
1client_helloversion, random, session ID, cipher suites, compression methods
2server_helloversion, random, session ID, cipher suite, compression method
11certificatea chain of X.509 certificates
12server_key_exchangeparameters and signature
13certificate_requestcertificate types, acceptable certification authorities
14server_hello_donenone
15certificate_verifysignature
16client_key_exchangeparameters and signature
20finisheda hash value

The four phases

Client                                          Server

Phase 1   client_hello          ------->
          <-------  server_hello

Phase 2   <-------  certificate
          <-------  server_key_exchange        (if the key exchange needs it)
          <-------  certificate_request         (if client authentication is wanted)
          <-------  server_hello_done

Phase 3   certificate           ------->       (if requested)
          client_key_exchange   ------->
          certificate_verify    ------->       (if a client certificate was sent)

Phase 4   change_cipher_spec    ------->
          finished              ------->
          <-------  change_cipher_spec
          <-------  finished

Phase 1: establish security capabilities

The client sends client_hello:

  • Version: the highest version the client supports.
  • Random: 32 bytes. In the original design, a four-byte timestamp and 28 random bytes. It guarantees that every handshake's secrets are fresh, and it is what defeats replaying an old handshake.
  • Session ID: zero to start a new session, or an existing session's ID to resume it.
  • Cipher suites: the client's list of acceptable combinations, in order of preference. Each names a key exchange method and a cipher spec.
  • Compression methods: the compression methods it supports.

The server answers with server_hello, carrying its own random value, the session ID, and the one cipher suite and compression method it has chosen from the client's lists.

The key exchange methods a cipher suite can name:

MethodHow the pre-master secret is agreed
RSAthe client picks it and encrypts it with the RSA public key in the server's certificate
Fixed Diffie-HellmanDiffie-Hellman with the server's public value fixed in its certificate
Ephemeral Diffie-HellmanDiffie-Hellman with fresh, one-time values, signed by the server; gives forward secrecy
Anonymous Diffie-HellmanDiffie-Hellman with no authentication at all, open to a man in the middle
Fortezzaa scheme defined only in SSL 3.0
munotes.in525

The SSL and TLS Handshake

Phase 2: server authentication and key exchange

The server sends its certificate chain, which the client validates exactly as the certificate chapters described. For ephemeral Diffie-Hellman it also sends server_key_exchange, its Diffie-Hellman values signed with the key in its certificate. If it wants the client to prove its identity too, it sends certificate_request. It ends with server_hello_done.

Phase 3: client authentication and key exchange

If asked, the client sends its certificate. Then client_key_exchange: for RSA, the pre-master secret encrypted with the server's public key; for Diffie-Hellman, the client's public value. If the client sent a certificate, certificate_verify proves it holds the matching private key by signing a hash of the handshake so far.

The pre-master secret for RSA is 48 bytes: the client's version, two bytes, followed by 46 random bytes. Putting the version inside the encrypted value lets the server detect an attacker who tried to push the connection down to an older version.

Phase 4: finish

Each side sends change_cipher_spec, switching on the keys just agreed, and then finished, the first message protected by them. The Finished message contains a value computed from the master secret and a hash of every handshake message so far. If anyone changed any handshake message, the two sides compute different values and the handshake fails.

From pre-master secret to keys

The master secret. TLS 1.2, RFC 5246 s.8.1:

master_secret = PRF(pre_master_secret, "master secret",
                    ClientHello.random + ServerHello.random)[0..47]

where PRF, the pseudo-random function, is built from HMAC-SHA256 by feeding its own output back in until enough bytes are produced. SSL 3.0 used an older construction, from RFC 6101:

master_secret = MD5(pre_master_secret + SHA('A'   + pre_master_secret + ClientHello.random + ServerHello.random)) +
                MD5(pre_master_secret + SHA('BB'  + pre_master_secret + ClientHello.random + ServerHello.random)) +
                MD5(pre_master_secret + SHA('CCC' + pre_master_secret + ClientHello.random + ServerHello.random))

The key block. The same PRF, applied to the master secret with the label "key expansion" and the two randoms, produces as many bytes as the cipher suite needs, and they are cut in order into the client write MAC key, the server write MAC key, the client write key, the server write key, and IVs if needed: the connection state of the previous chapter.

The Finished value. For TLS 1.2:

verify_data = PRF(master_secret, finished_label, Hash(handshake_messages))[0..11]

with the label "client finished" or "server finished", and the hash taken over every handshake message before this one.

The run: a real handshake, recomputed

On this computer OpenSSL's own client and server ran a TLS 1.2 handshake using RSA key exchange and the suite TLS_RSA_WITH_AES_128_CBC_SHA256. OpenSSL printed every handshake message as it was sent and received, and wrote down the pre-master and master secrets it used. The first file below is those messages; the second is OpenSSL's key log. The listing recomputes everything from them, using the formulas above.

munotes.in526

The SSL and TLS Handshake

# ClientHello client to server
0100008903036753c540559a2328753817618a04f19c5515f4ea01ccd4cf8150
c41582fecf8f000002003c0100005eff01000100000000190017000014657861
6d2e636f6c6c6567652e6578616d706c650023000000160000000d0030002e04
0305030603080708080809080a080b0804080508060401050106010303020303
01020103020202040205020602
# ServerHello server to client
020000350303f1ad934a9cf09c2ad5437eba6fcfa27039ed283a9b7be694cb42
5863a32cfbf000003c00000dff010001000023000000160000
# Certificate server to client
0b0003290003260003233082031f30820207a00302010202143479783543a1d5
5a682a6d8124267af3a948c1f9300d06092a864886f70d01010b0500301f311d
301b06035504030c146578616d2e636f6c6c6567652e6578616d706c65301e17
0d3236303933303130353530325a170d3236313033303130353530325a301f31
1d301b06035504030c146578616d2e636f6c6c6567652e6578616d706c653082
0122300d06092a864886f70d01010105000382010f003082010a0282010100d6
30a3b9975caf709e7c8835665a955861284cd0e229087400f34dd0034d931a1c
ed55db6e43f63fea5d4e1e06254c6f08e4f51677613914c119c62530dea7c9a9
b53845ebca09421c25389d78c1d5f3aa43e17e6e4c509d76f126402488bb64b0
ba0aa623d3597b6451994942e14736d99ff859f2e1ea10e306027005d6e1232f
41009ff7fe262d371da96712bd05d7551b3deddb30961e37658e1220171fe622
3fb3f863fbfe8c608033db9daa30121f653b7cdfa698bd27986660229711c5dd
29f95113a5bd446d1d2ddfc781af0a94a2296f7a9c1eb26e4f3febc81d656142
ad2dbd80521ab8f156d246df2a21b79e48ca66c270997637abb72810a553f302
03010001a3533051301d0603551d0e0416041436022e3abeb1ed7bb286e179b3
44d96133a109b6301f0603551d2304183016801436022e3abeb1ed7bb286e179
b344d96133a109b6300f0603551d130101ff040530030101ff300d06092a8648
86f70d01010b050003820101009d232d9a0cfcbb54850440839992f1d7af6029
639979b24546472e868fc6acc283206a893a95c5697d3562b8640c3d24ed8119
c91527bab3f895291e045d9d925e6431bff9bab5c7d06cfa4e904c7e45ab67cd
d0599379b9d35c65848173dfd7c91131f174058c1a6c6db9cbd8208a4cd3a16f
c67141abc628ae4315f1d73d2ae5575609c657fc6f793c4a1801857c2709f291
16b3cbbf688ad4bb247d393be5caa750edec1b0f1bdb20c8de47e23bb422cbc7
40ed066a9c17ee715272ef47260ecde1b64f0bacdc2b498862955c3d5dfadc7c
3da95ec1e98eb8baf89fc54acd786815792a1cc2fdbbbab87a57f9cddcca9ff2
f7acbd52c43b0fd7062dae4c69
# ServerHelloDone server to client
0e000000
# ClientKeyExchange client to server
1000010201006a0bb8ddef0e3937d7d77a3bd24e7ac10b5e18c44a7cf056e2b7
7c5d2bd0bce6dfca1e2bf4092ef0c38a2f68dacd7ea7134eeb7b23772d43729c
85f464934b6ffefd561bf19e4142e6dcbe3c15dfe179e2f2cb0ad3dcd95d9a06
400df47a72fad1c05b76e5ece67f79e293915177ccb539cd868fbbf5fc67c6c0
d71383c0f0f5134603fc7e7e0c84b7af8775f8088595b13f5d63e8f6295f44d5
73ec926caf0ffd74decf17b963513addd35fa7ea364839401c10191d9a582fae
128b1e1df1e2bd733ea688e7e54e29d98cdad2b2e5a6f3eb6c7af0fa63d611e1
c04c8d4d729bf0376302a9c1ddac8005912a56775651afd7777ac6ce2f9eb2ec
bc170cf529c3
# Finished client to server
1400000c0cfd59f2f4015b6f8d5809cd
# NewSessionTicket server to client
040000a600001c2000a0e6e1a1c77717c089be99713eb2b9714b31597275110c
2e61c90bfc3294198de5d1665e9380e9e46e438f15a6dea3a7afb8c4f016b555
6545047c600e324b759b2962e773a82d5f43981d05cb6736b9d4352c1319d9f7
2e4c6f03de3dc533f4eaa70eeadd3f4779a1f5581393235cda5041594b5b5d4e
6b10002c333822022155c4b74b18e7318301b9b07fcf63126d9262f09cb82289
5c07188bff04d09e844e
# Finished server to client
1400000cb72a77d8841afc718abf4730
# SSL/TLS secrets log file, generated by OpenSSL
RSA 6a0bb8ddef0e3937 0303640ec631a477c7d7780cb551d1e84511de2e3bb88b35f065644768812c042c703cd47df2f47ce6a06e814cc99770
CLIENT_RANDOM 6753c540559a2328753817618a04f19c5515f4ea01ccd4cf8150c41582fecf8f 8dbd6a60bce093321996de14e2ebff49a3907ccfd9e040b4ef5e1c5ae62d5f543f32c3adbecc1a0f1ae0411c11bb0c82
# A real TLS 1.2 handshake between OpenSSL's client and server, recomputed from its messages.
import hashlib, hmac

messages = []                                   # (name, direction, bytes), in wire order
for line in open('handshake-tls12.hex').read().split('\n'):
    if line.startswith('# '):
        name, direction = line[2:].split(' ', 1)
        messages.append([name, direction, b''])
    elif line:
        messages[-1][2] += bytes.fromhex(line)

log = [l.split() for l in open('keylog.txt').read().split('\n') if l and l[0] != '#']
pre_master = bytes.fromhex(next(l for l in log if l[0] == 'RSA')[2])
logged_random, logged_master = next(l for l in log if l[0] == 'CLIENT_RANDOM')[1:]

PHASE = {'ClientHello': 1, 'ServerHello': 1, 'Certificate': 2, 'ServerHelloDone': 2,
         'ClientKeyExchange': 3, 'Finished': 4, 'NewSessionTicket': 4}
print('THE HANDSHAKE AS IT CROSSED THE WIRE (TLS 1.2, RSA key exchange, AES128-SHA256)')
for name, direction, body in messages:
    print('  phase %d  %-18s %-17s type %2d, %4d bytes' % (PHASE[name], name, direction,
                                                           body[0], len(body)))
ch, sh = messages[0][2], messages[1][2]
client_random, server_random = ch[6:38], sh[6:38]
suite = sh[39 + sh[38]:41 + sh[38]]
cke = messages[4][2]
print()
print('WHAT THE HELLOS AND THE KEY EXCHANGE CARRY')
print('  client random  %s...' % client_random.hex()[:32])
print('  server random  %s...' % server_random.hex()[:32])
print('  suite chosen   0x%s, TLS_RSA_WITH_AES_128_CBC_SHA256' % suite.hex())
print('  ClientKeyExchange: %d bytes of RSA ciphertext, beginning %s' % (
      int.from_bytes(cke[4:6], 'big'), cke[6:14].hex()))
print('  OpenSSL\'s key log tags its pre-master with the same 8 bytes:',
      next(l for l in log if l[0] == 'RSA')[1] == cke[6:14].hex())
print('  that pre-master secret is 48 bytes, starting %s,' % pre_master[:2].hex())
print('  which is the client\'s version, TLS 1.2, as RFC 5246 s.7.4.7.1 requires')

def p_sha256(secret, seed, n):                  # RFC 5246 s.5
    out, a = b'', seed
    while len(out) < n:
        a = hmac.new(secret, a, hashlib.sha256).digest()
        out += hmac.new(secret, a + seed, hashlib.sha256).digest()
    return out[:n]

def prf(secret, label, seed, n):
    return p_sha256(secret, label + seed, n)

master = prf(pre_master, b'master secret', client_random + server_random, 48)
print()
print('THE SECRETS, RECOMPUTED')
print('  master_secret = PRF(pre_master, "master secret", client_random + server_random)')
print('  equals the master secret OpenSSL logged :', master.hex() == logged_master
      and client_random.hex() == logged_random)
block = prf(master, b'key expansion', server_random + client_random, 2 * 32 + 2 * 16)
print('  key block: client MAC key %s..., server MAC key %s...,' % (block[:4].hex(),
                                                                  block[32:36].hex()))
print('             client write key %s..., server write key %s...' % (
      block[64:68].hex(), block[80:84].hex()))

bodies = [m[2] for m in messages]
client_verify = prf(master, b'client finished', hashlib.sha256(b''.join(bodies[:5])).digest(), 12)
server_verify = prf(master, b'server finished', hashlib.sha256(b''.join(bodies[:7])).digest(), 12)
print('  client Finished = PRF(master, "client finished", SHA-256 of the handshake so far)')
print('    equals the 12 bytes the client sent  :', client_verify == bodies[5][4:])
print('  server Finished, over everything before it, NewSessionTicket included')
print('    equals the 12 bytes the server sent  :', server_verify == bodies[7][4:])
changed = bytearray(bodies[0])
changed[40] ^= 1                                 # the ClientHello, as an attacker altered it
print('  the client Finished over a ClientHello with one bit changed would match:',
      prf(master, b'client finished',
          hashlib.sha256(bytes(changed) + b''.join(bodies[1:5])).digest(), 12) == bodies[5][4:])

def ssl3_master(pre, cr, sr):                    # RFC 6101 s.6.1
    return b''.join(hashlib.md5(pre + hashlib.sha1(x + pre + cr + sr).digest()).digest()
                    for x in (b'A', b'BB', b'CCC'))
print()
print('SSL 3.0\'S FORMULA ON THE SAME INPUTS gives a different 48 bytes:',
      ssl3_master(pre_master, client_random, server_random) != master)
munotes.in527

The SSL and TLS Handshake

THE HANDSHAKE AS IT CROSSED THE WIRE (TLS 1.2, RSA key exchange, AES128-SHA256)
  phase 1  ClientHello        client to server  type  1,  141 bytes
  phase 1  ServerHello        server to client  type  2,   57 bytes
  phase 2  Certificate        server to client  type 11,  813 bytes
  phase 2  ServerHelloDone    server to client  type 14,    4 bytes
  phase 3  ClientKeyExchange  client to server  type 16,  262 bytes
  phase 4  Finished           client to server  type 20,   16 bytes
  phase 4  NewSessionTicket   server to client  type  4,  170 bytes
  phase 4  Finished           server to client  type 20,   16 bytes

WHAT THE HELLOS AND THE KEY EXCHANGE CARRY
  client random  6753c540559a2328753817618a04f19c...
  server random  f1ad934a9cf09c2ad5437eba6fcfa270...
  suite chosen   0x003c, TLS_RSA_WITH_AES_128_CBC_SHA256
  ClientKeyExchange: 256 bytes of RSA ciphertext, beginning 6a0bb8ddef0e3937
  OpenSSL's key log tags its pre-master with the same 8 bytes: True
  that pre-master secret is 48 bytes, starting 0303,
  which is the client's version, TLS 1.2, as RFC 5246 s.7.4.7.1 requires

THE SECRETS, RECOMPUTED
  master_secret = PRF(pre_master, "master secret", client_random + server_random)
  equals the master secret OpenSSL logged : True
  key block: client MAC key 213a99d8..., server MAC key f15fe4ac...,
             client write key ce4b582c..., server write key 58abae28...
  client Finished = PRF(master, "client finished", SHA-256 of the handshake so far)
    equals the 12 bytes the client sent  : True
  server Finished, over everything before it, NewSessionTicket included
    equals the 12 bytes the server sent  : True
  the client Finished over a ClientHello with one bit changed would match: False

SSL 3.0'S FORMULA ON THE SAME INPUTS gives a different 48 bytes: True
munotes.in528

The SSL and TLS Handshake

What the run establishes, in order.

The four phases, as they happened. Phase 1, the two hellos; phase 2, the server's certificate, 813 bytes, and server_hello_done, just 4 bytes, a type and an empty length; phase 3, the client_key_exchange; phase 4, the two Finished messages. The server also sent a NewSessionTicket, a TLS extension for resuming the session later, before its own change_cipher_spec.

RSA key exchange is visible in the bytes. The ClientKeyExchange carries 256 bytes, one 2,048-bit RSA ciphertext. OpenSSL's key log tags the pre-master secret with the ciphertext's first eight bytes, and they match. The pre-master begins 0303, TLS 1.2's version number, exactly as RFC 5246 lays it out.

The master secret is reproduced. Applying RFC 5246's PRF to the pre-master and the two randoms gives exactly the master secret OpenSSL computed. From it the key block gives the four keys: a MAC key and an encryption key in each direction.

Both Finished messages are reproduced. The client's 12 bytes, computed over the hash of the five messages before it, match what the client sent; the server's, computed over everything before it including the client's Finished and the ticket, match what the server sent. Change a single bit of the ClientHello, as an attacker downgrading the offer would, and the Finished value no longer matches, which is exactly how the handshake detects tampering with its own negotiation.

SSL 3.0's formula gives a different master secret from the same inputs: the two versions are not interchangeable, which is one reason a connection must agree its version first.

The Change Cipher Spec Protocol

The simplest protocol in SSL: one message, one byte, with the value 1. It tells the other side that from the next record on, the sender will protect its records with the newly negotiated keys. On sending it, a side copies its pending write state into its current write state; on receiving it, the pending read state becomes current.

The Alert Protocol

An alert is two bytes: a level, 1 for warning or 2 for fatal, and a description. A fatal alert ends the connection at once, and the session may not be resumed.

Always fatalMeaning
unexpected_message (10)a message arrived out of turn
bad_record_mac (20)a record's MAC did not verify
decompression_failure (30)unpacking failed, or gave too much
handshake_failure (40)no acceptable set of security parameters
illegal_parameter (47)a field in a handshake message was out of range
Other alertsMeaning
close_notify (0)the sender will send no more messages on this connection
no_certificate (41)SSL 3.0 only: no suitable certificate is available
bad_certificate (42)a certificate was corrupt or its signature did not verify
unsupported_certificate (43)the certificate type is not supported
certificate_revoked (44)the certificate has been revoked
certificate_expired (45)the certificate has expired
certificate_unknown (46)some other problem with the certificate
munotes.in529

The SSL and TLS Handshake

close_notify matters more than it looks. Each side must send it before closing. Without it, an attacker could cut a connection short, and the receiver could not tell a deliberately truncated message from a complete one: RFC 5246 requires the closing exchange "in order to avoid a truncation attack".

What changed later

The master secret was not bound to the whole handshake. RFC 7627 (September 2015) explains the flaw: the master secret "is not cryptographically bound to important session parameters such as the server certificate", so an attacker could set up two sessions, one with a client and one with a server, with the same master secret, the triple handshake attack. Its fix, the extended master secret, computes the master secret from a hash of the whole handshake instead of just the two randoms. OpenSSL uses it by default; the run switched it off so that the textbook formula would apply.

RSA key exchange has no forward secrecy. Whoever later obtains the server's private key can decrypt every recorded ClientKeyExchange and so every past session. TLS 1.3 removed RSA key exchange entirely, keeping only ephemeral key exchange; the next chapter shows TLS 1.3's handshake. In July 2026 RFC 10015 closed the door in TLS 1.2 as well: "Clients MUST NOT offer and servers MUST NOT select RSA cipher suites in (D)TLS 1.2 connections". The run uses RSA key exchange because it is the method the syllabus teaches and the easiest to follow; no server should be set up this way today.

New names for the same secrets. RFC 9846 (July 2026), which now specifies TLS 1.3 in place of RFC 8446, renames TLS 1.2's master secret the main secret, the pre-master secret the preliminary secret, and the extended master secret the extended main secret. The label inside the PRF stays "master secret" "for compatibility", so the run's computation is unchanged. An exam answer uses the textbook's names; the new ones show that it is current.

Distinctions that carry marks

RSA key exchangeEphemeral Diffie-Hellman
Pre-master secret made bythe client, encrypted to the server's keyboth sides, from fresh Diffie-Hellman values
server_key_exchange sentnoyes, the values signed by the server
Forward secrecynoyes
In TLS 1.3removedthe only choice
Change Cipher SpecFinished
Protocolits own, content type 20the Handshake Protocol, content type 22
Contentone byte, value 112 bytes computed from the master secret and all handshake messages
Purposeswitch on the new keysprove both sides saw the same, unaltered handshake
munotes.in530

The SSL and TLS Handshake

What beginners get wrong here

Saying the master secret is sent. It is never sent; each side computes it. Only the pre-master secret travels, encrypted, and only in RSA key exchange.

Swapping the phases. Hellos first, then the server's certificate and key exchange, then the client's, and the change cipher spec and Finished last.

Forgetting the randoms. Without both randoms in the master secret, a recorded handshake could be replayed to produce the same keys.

Treating Finished as a formality. It is the handshake's own integrity check: it is what detects an attacker who altered the cipher-suite list to force weak algorithms.

Calling change_cipher_spec a handshake message. It is its own protocol, sent as its own record type.

Quick revision

  • Handshake message: type (1 byte), length (3 bytes), content. Types 0, 1, 2, 11, 12, 13, 14, 15, 16, 20.
  • Phase 1: client_hello (version, random, session ID, cipher suites, compression), server_hello (the choice).
  • Phase 2: certificate, server_key_exchange, certificate_request, server_hello_done.
  • Phase 3: certificate, client_key_exchange, certificate_verify.
  • Phase 4: change_cipher_spec, finished, from both sides.
  • Key exchange: RSA, fixed DH, ephemeral DH (forward secrecy), anonymous DH, Fortezza.
  • Pre-master (RSA): 48 bytes, version + 46 random. master_secret = PRF(pre_master, "master secret", client_random + server_random), 48 bytes; key block by PRF with "key expansion".
  • Finished = PRF(master, label, hash of all handshake messages), 12 bytes.
  • Change Cipher Spec: one byte, value 1. Alert: level (warning 1, fatal 2) and description; close_notify stops truncation.
  • Later: extended master secret (RFC 7627) against the triple handshake; TLS 1.3 removed RSA key exchange, and RFC 10015 (July 2026) forbids it in TLS 1.2 too.
  • Since RFC 9846 (July 2026) the master and pre-master secrets are called the main and preliminary secrets; the PRF label is still "master secret".

Test yourself

1. Describe the four phases of the SSL handshake. In phase 1 the client and server establish security capabilities: the client_hello offers a version, a random value, a session ID, a list of cipher suites and compression methods, and the server_hello chooses from them and supplies the server's random. In phase 2 the server authenticates and exchanges keys: it sends its certificate, a server_key_exchange if the method needs one, optionally a certificate_request, and a server_hello_done. In phase 3 the client sends its certificate if asked, a client_key_exchange containing the encrypted pre-master secret or its Diffie-Hellman value, and a certificate_verify if it sent a certificate. In phase 4 each side sends change_cipher_spec and then a Finished message protected by the new keys.

2. What does the client_hello message contain, and why is the random value included? The highest protocol version the client supports, a 32-byte random value, a session ID, the list of cipher suites it accepts in order of preference, and the compression methods it supports. The random value, together with the server's, goes into the master secret, so every handshake produces fresh keys and a recorded handshake cannot be replayed to reproduce old ones.

munotes.in531

The SSL and TLS Handshake

3. How is the master secret computed in TLS 1.2, and how are the keys derived from it? The master secret is the first 48 bytes of PRF(pre-master secret, "master secret", client random followed by server random), where the PRF is built from HMAC-SHA256. The key block is then PRF(master secret, "key expansion", server random followed by client random), cut in order into the client write MAC key, the server write MAC key, the client write key, the server write key and any IVs.

4. What is the purpose of the Finished message? It is the first message protected by the new keys, and it contains a value computed from the master secret and a hash of all the handshake messages exchanged so far. If an attacker altered any handshake message, for example to remove strong cipher suites from the client's offer, the two sides compute different values and the handshake fails. It therefore confirms that both sides hold the same master secret and saw the same handshake.

5. Describe the Change Cipher Spec and Alert protocols. The Change Cipher Spec protocol consists of a single message of one byte with the value 1, which tells the peer that subsequent records will be protected with the newly negotiated parameters, and causes the pending state to become the current state. The Alert protocol carries two-byte messages giving a level, warning or fatal, and a description such as bad_record_mac or handshake_failure; a fatal alert ends the connection immediately, and close_notify announces an orderly close so that a truncated connection can be detected.

6. Why is RSA key exchange weaker than ephemeral Diffie-Hellman, and what did TLS 1.3 do about it? With RSA key exchange the pre-master secret travels encrypted under the server's long-term key, so anyone who later obtains that key can decrypt recorded handshakes and all the traffic they protected: there is no forward secrecy. With ephemeral Diffie-Hellman the secret comes from one-time values that are discarded, so a later compromise of the server's key does not expose past sessions. TLS 1.3 removed RSA key exchange and uses only ephemeral key exchange, and since July 2026 RFC 10015 forbids RSA key exchange in TLS 1.2 as well.

Contents This chapter on its own page

munotes.in532

Chapter Eighty

TLS, and What Each Version Fixed

Syllabus topic Module 2, "Web Security: Secure Socket Layer and Transport Layer Security"

In one line

TLS is SSL taken over by the IETF and then repaired version by version: TLS 1.0 (1999) was SSL 3.0 with HMAC and a new way of expanding keys; 1.1 fixed the CBC initialisation vector; 1.2 let the hash be chosen and brought in authenticated encryption; and 1.3 (2018) rebuilt the handshake, with one round trip, forward secrecy always, and everything after the ServerHello encrypted.

In the words an answer should use: Transport Layer Security (TLS) is an IETF standards-track protocol, first defined in RFC 2246, whose aim is an Internet standard version of SSL. TLS 1.0 is very close to SSLv3 and differs in the version number, the message authentication code (TLS uses HMAC), the pseudorandom function that expands secrets into keys, the alert codes, the cipher suites and client certificate types, the certificate_verify and finished messages, the cryptographic computations, and padding.

From SSL to TLS

SSL was Netscape's. Version 2.0 was described in February 1995 and version 3.0 in November 1996; RFC 6101, which records SSL 3.0 for history, calls it "the November 18, 1996, version of the protocol". The IETF then took the protocol over and in January 1999 published it as TLS 1.0, RFC 2246, which says its differences from SSL 3.0 "are not dramatic, but they are significant enough that TLS 1.0 and SSL 3.0 do not interoperate".

Why TLS 1.0 is "version 3.1". On the wire TLS 1.0 carries the version {3, 1}, and RFC 2246 explains: "The version value 3.1 is historical: TLS version 1.0 is a minor modification to the SSL 3.0 protocol". TLS 1.1 is {3, 2} and TLS 1.2 is {3, 3}. TLS 1.3 is 0x0304, but, as the run shows, not where the others put it.

How TLS 1.0 differs from SSL 3.0

This is the comparison the syllabus's textbook sets out, checked here against both specifications.

PointSSL 3.0TLS 1.0
Version number3.03.1
Record MACSSL's own nested hash with pad_1 and pad_2, as the record-layer chapter ranHMAC, which also covers the version field
Master secretMD5 and SHA-1 nested three times, with the salts 'A', 'BB' and 'CCC'PRF(pre_master_secret, "master secret", the two randoms)
Expanding secrets into keysnested MD5 and SHA-1a pseudorandom function, PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR P_SHA-1(S2, label + seed), with S1 and S2 the two halves of the secret
Finished36 bytes: MD5 and SHA-1 over the handshake messages, a sender code, the master secret and the pads12 bytes: PRF(master_secret, "client finished" or "server finished", MD5 and SHA-1 of the handshake messages)
certificate_verifyhashes over the handshake messages, the master secret and the padshashes over the handshake messages alone
Alerts12 codes, among them no_certificate (41)no_certificate dropped and 12 added, among them decryption_failed, record_overflow, unknown_ca, decode_error, decrypt_error, protocol_version, internal_error and user_canceled
Cipher suitesinclude FortezzaFortezza dropped
Client certificate typesseven, among them rsa_ephemeral_dh, dss_ephemeral_dh and fortezza_keafour: rsa_sign, dss_sign, rsa_fixed_dh, dss_fixed_dh
Paddingless than one blockany length up to 255 bytes, so that the lengths of messages can be hidden
munotes.in533

TLS, and What Each Version Fixed

What SSL 3.0 had already fixed. RFC 6176 lists what was wrong with SSL 2.0: message authentication used MD5; the handshake was not protected, which "permits a man-in-the-middle to trick the client into picking a weaker cipher suite"; one key served for both integrity and encryption; and a forged TCP FIN could end a session unnoticed. SSL 3.0 answered the last three: its Finished message checks every handshake message, it derives separate MAC and encryption keys, and its close_notify alert lets each side tell a real end from a truncation.

What each version fixed

VersionPublishedDefined inWhat it fixed or addedStatus on 30 September 2026
SSL 2.0February 1995Netscapethe first public versionprohibited, RFC 6176 (March 2011)
SSL 3.0November 1996Netscape; recorded as RFC 6101 in 2011a protected handshake, separate MAC and encryption keys, closure alertsprohibited, RFC 7568 (June 2015)
TLS 1.0January 1999RFC 2246an IETF standard, with HMAC and the PRFdeprecated, RFC 8996 (March 2021)
TLS 1.1April 2006RFC 4346an explicit IV "to protect against CBC attacks"deprecated, RFC 8996
TLS 1.2August 2008RFC 5246a PRF chosen by the cipher suite (SHA-256), one named hash in signatures, authenticated encryption such as AES-GCMin use, but RSA and finite-field Diffie-Hellman key exchange forbidden in it by RFC 10015 (July 2026)
TLS 1.3August 2018RFC 8446, replaced in July 2026 by RFC 9846a new handshake: one round trip, forward secrecy always, AEAD only, encrypted after the ServerHellocurrent

TLS 1.1 also answered the padding-oracle attack the record-layer chapter described, by sending bad_record_mac rather than decryption_failed for a padding error, so that the two failures look the same. TLS 1.2 removed MD5 and SHA-1 from the PRF: "All cipher suites in this document use P_SHA256", as the handshake chapter's run did.

TLS 1.3: a new handshake

Two message diagrams, one above the other. TLS 1.2: the client sends ClientHello; the server replies with ServerHello, Certificate, ServerKeyExchange and ServerHelloDone; the client sends ClientKeyExchange, ChangeCipherSpec and an encrypted Finished; the server replies with ChangeCipherSpec and an encrypted Finished; only then does the client send encrypted application data. That is two round trips. TLS 1.3: the client sends ClientHello with a key share; the server replies with ServerHello and its key share, followed at once by encrypted EncryptedExtensions, Certificate, CertificateVerify and Finished; the client sends its encrypted Finished and application data. That is one round trip.

Figure 80.1 TLS 1.2 needs two round trips before the client can send data; TLS 1.3 needs one, and hides the certificate.

RFC 9846 lists the major differences from TLS 1.2. In an answer they come to seven.

  • One round trip. The client sends a key share in its very first message, guessing which group the server will accept, so the server can reply with its own share, its certificate and its Finished in one flight. If the guess is wrong the server sends a HelloRetryRequest, which costs a round trip.
  • Forward secrecy always. "Static RSA and Diffie-Hellman cipher suites have been removed; all public-key based key exchange mechanisms now provide forward secrecy."
  • Authenticated encryption only. The legacy algorithms were pruned, and "Those that remain are all Authenticated Encryption with Associated Data (AEAD) algorithms". Compression is gone, and with it the CRIME attack.
  • An encrypted handshake. "All handshake messages after the ServerHello are now encrypted", the server's certificate included.
  • A new key schedule, built on HKDF, which the second run reproduces.
  • Version negotiation in an extension. The version travels in a supported_versions list, and the ChangeCipherSpec protocol is removed "except when needed for middlebox compatibility".
  • Shorter cipher suites. A TLS 1.3 suite names only the AEAD cipher and the hash, TLS_AES_256_GCM_SHA384 for example; the key exchange and the signature are negotiated separately.
munotes.in534

TLS, and What Each Version Fixed

Zero round trips, at a price. A client returning to a server it knows can send 0-RTT "early data" in its first message. RFC 9846 warns that the security of 0-RTT data is weaker: it is not forward secret, and "There are no guarantees of non-replay between connections". A client may therefore send as early data only what it deems "safe to be replayed": fetching a page, say, never a payment.

The run: munotes.in, and the mark that stops a downgrade

The first listing reads two real handshakes with munotes.in, captured on 30 September 2026: one by a client offering TLS 1.3, and one by a client offering only TLS 1.2. It reports what the server chose, looks at the last bytes of the server's random value, and checks the server's ECDSA signature itself, on the NIST P-256 curve, with the public key from munotes.in's certificate.

# TLS 1.3 ServerHello, its first 96 of 1,210 bytes
02 00 04 b6 03 03 50 2c 7c e9 08 9b bb d2 ad 6c
46 ae ba ba eb 1a 58 21 d0 e7 8c fc 3e 96 a1 b8
79 8e 3c d4 15 52 20 46 e3 1c b7 1d f6 2d 32 bf
8c 77 8d 3d 23 4c 37 95 19 1a e7 18 91 80 27 0e
3e 31 eb 42 d3 f5 2f 13 02 00 04 6e 00 2b 00 02
03 04 00 33 04 64 11 ec 04 60 95 23 7f f8 f7 3f
# TLS 1.2 ClientHello, its first 38 bytes
01 00 00 c7 03 03 bc 8d 5c 0f 58 10 2b c7 2e 45
4f 7e 44 af 60 68 c8 d8 c6 3b a6 a3 78 35 f0 fc
b5 99 b9 df 47 62
# TLS 1.2 ServerHello
02 00 00 41 03 03 b7 e7 ec d1 a2 ca 5b 71 a2 f6
61 c0 f7 f0 16 3d c1 c5 a2 fd 19 d9 0e 6e 44 4f
57 4e 47 52 44 01 00 c0 2c 00 00 19 ff 01 00 01
00 00 00 00 00 00 0b 00 04 03 00 01 02 00 23 00
00 00 17 00 00
# TLS 1.2 ServerKeyExchange
0c 00 00 6e 03 00 1d 20 7b 33 36 05 81 a9 e8 05
be 23 c0 54 5a 0a 74 a1 11 ba 36 0a 39 ea 0e 9e
7d 41 55 65 79 45 1e 27 04 03 00 46 30 44 02 20
58 36 10 85 f0 3a d8 79 3a d0 f8 5a 2e 99 c9 91
73 b1 51 4e cd 64 1a 1f c1 54 00 db 7a 90 09 49
02 20 3c 1b c3 42 df d6 8e 88 cb 49 2f de 28 38
ca 0f e3 11 3b 66 7a a1 7a 5e 89 e4 16 d2 f3 f8
bc c9
# public key in the certificate munotes.in sent
04 b6 50 14 d1 56 d0 7c 49 38 1d 3a d7 e6 12 16
b9 4a 62 43 6d 06 1e 13 55 ba a1 3e 64 35 43 f5
42 d2 ac e7 f8 2a 0f 2a 4a 7d 20 b4 b4 0f 46 4e
5f 54 17 ff d6 55 2e b3 c8 a8 10 a3 00 57 6f 93
b7
munotes.in535

TLS, and What Each Version Fixed

# munotes.in on 30 September 2026: what TLS 1.3 chose, and the marker that defeats a downgrade.
import hashlib

wire, title = {}, None
for line in open('munotes-handshakes.hex').read().split('\n'):
    if line.startswith('# '):
        title = line[2:].split(',')[0]
        wire[title] = b''
    elif line:
        wire[title] += bytes.fromhex(line)

# ---- 1. a browser offering TLS 1.3: what the server chose --------------------------------
sh = wire['TLS 1.3 ServerHello']
at = 4 + 2 + 32
at += 1 + sh[at]                                        # the session id echoed back
suite = sh[at:at + 2]
at += 2 + 1 + 2                                         # suite, compression, extensions length
version = sh[at + 4:at + 6]                             # supported_versions: the real version
at += 4 + int.from_bytes(sh[at + 2:at + 4], 'big')
group = int.from_bytes(sh[at + 4:at + 6], 'big')        # key_share
share = int.from_bytes(sh[at + 6:at + 8], 'big')
print('1. OFFERED TLS 1.3 (ServerHello of %d bytes)' % (int.from_bytes(sh[1:4], 'big') + 4))
print('   version field 0x%s, supported_versions 0x%s: TLS 1.3' % (sh[4:6].hex(), version.hex()))
print('   cipher suite 0x%s: TLS_AES_256_GCM_SHA384' % suite.hex())
print('   key share group 0x%04X (%d): X25519MLKEM768' % (group, group))
print('   server share %d bytes = 1,088 ML-KEM-768 + 32 X25519: %s' % (share, share == 1088 + 32))

# ---- 2. a client offering only TLS 1.2: the same server marks its random -------------------
client_random = wire['TLS 1.2 ClientHello'][6:38]
sh12 = wire['TLS 1.2 ServerHello']
server_random = sh12[6:38]
at = 38 + 1 + sh12[38]
print()
print('2. OFFERED ONLY TLS 1.2')
print('   version 0x%s, cipher suite 0x%s: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384'
      % (sh12[4:6].hex(), sh12[at:at + 2].hex()))
print('   last 8 bytes of the server random:', server_random[-8:])
MARK_12 = bytes.fromhex('444F574E47524401')           # RFC 8446 s.4.1.3, RFC 9846 s.4.2.3
print('   equal to the TLS 1.2 downgrade marker:', server_random[-8:] == MARK_12)
print('   a client that had offered TLS 1.3 must abort: %s'
      % ('illegal_parameter' if server_random[-8:] == MARK_12 else 'no'))

# ---- 3. why an attacker cannot erase the marker: the server SIGNED both randoms ------------
# P-256, FIPS 186-4 D.1.2.3 (y^2 = x^3 - 3x + b)
p = 115792089210356248762697446949407573530086143415290314195533631308867097853951
n = 115792089210356248762697446949407573529996955224135760342422259061068512044369
b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
G = (0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
     0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5)

def add(P1, P2):
    if P1 is None or P2 is None:
        return P1 or P2
    (x1, y1), (x2, y2) = P1, P2
    if x1 == x2 and (y1 + y2) % p == 0:
        return None
    if P1 == P2:
        m = (3 * x1 * x1 - 3) * pow(2 * y1, -1, p)
    else:
        m = (y2 - y1) * pow(x2 - x1, -1, p)
    x3 = (m * m - x1 - x2) % p
    return x3, (m * (x1 - x3) - y1) % p

def mul(k, P):
    R = None
    while k:
        if k & 1:
            R = add(R, P)
        P, k = add(P, P), k >> 1
    return R

def ecdsa_verify(public, message, r, s):              # FIPS 186-4 s.6.4, with SHA-256
    e = int.from_bytes(hashlib.sha256(message).digest(), 'big')
    w = pow(s, -1, n)
    X = add(mul(e * w % n, G), mul(r * w % n, public))
    return X is not None and X[0] % n == r

key = wire['public key in the certificate munotes.in sent']
public = (int.from_bytes(key[1:33], 'big'), int.from_bytes(key[33:65], 'big'))
ske = wire['TLS 1.2 ServerKeyExchange'][4:]
params = ske[:4 + ske[3]]                               # curve type, named curve, public value
algorithm, sig = ske[len(params):len(params) + 2], ske[len(params) + 4:]
r_len = sig[3]
r = int.from_bytes(sig[4:4 + r_len], 'big')
s = int.from_bytes(sig[6 + r_len:], 'big')
print()
print('3. THE SERVERKEYEXCHANGE SIGNATURE')
print('   named curve 0x%s (x25519), signature scheme 0x%s (ecdsa_secp256r1_sha256)'
      % (params[1:3].hex(), algorithm.hex()))
print('   certificate key is on P-256:',
      (public[1] ** 2 - public[0] ** 3 + 3 * public[0] - b) % p == 0)
print('   signature over client random + server random + params verifies:',
      ecdsa_verify(public, client_random + server_random + params, r, s))
erased = server_random[:24] + bytes(8)
assert erased != server_random
print('   the same signature with the marker erased verifies:',
      ecdsa_verify(public, client_random + erased + params, r, s))
munotes.in536

TLS, and What Each Version Fixed

1. OFFERED TLS 1.3 (ServerHello of 1210 bytes)
   version field 0x0303, supported_versions 0x0304: TLS 1.3
   cipher suite 0x1302: TLS_AES_256_GCM_SHA384
   key share group 0x11EC (4588): X25519MLKEM768
   server share 1120 bytes = 1,088 ML-KEM-768 + 32 X25519: True

2. OFFERED ONLY TLS 1.2
   version 0x0303, cipher suite 0xc02c: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
   last 8 bytes of the server random: b'DOWNGRD\x01'
   equal to the TLS 1.2 downgrade marker: True
   a client that had offered TLS 1.3 must abort: illegal_parameter

3. THE SERVERKEYEXCHANGE SIGNATURE
   named curve 0x001d (x25519), signature scheme 0x0403 (ecdsa_secp256r1_sha256)
   certificate key is on P-256: True
   signature over client random + server random + params verifies: True
   the same signature with the marker erased verifies: False
munotes.in537

TLS, and What Each Version Fixed

What the run establishes, in order.

munotes.in speaks TLS 1.3, with a post-quantum key exchange. The ServerHello's version field says 0x0303, TLS 1.2, and the real version, 0x0304, is in the supported_versions extension: RFC 9846 keeps the old field fixed because servers "incorrectly implemented version negotiation". The key share is group 4588, X25519MLKEM768, which RFC 10024 (August 2026) defines as ML-KEM-768, the post-quantum method of FIPS 203, combined with X25519, "to provide security as long as at least one of the component algorithms remains secure". Its 1,120 bytes are 1,088 of ML-KEM and 32 of X25519.

Asked for TLS 1.2, the server agrees but leaves a mark. The last eight bytes of its random value spell DOWNGRD followed by the byte 01, which RFC 9846 requires of any TLS 1.3 server that negotiates TLS 1.2. This client offered only TLS 1.2, so it carries on. A client that had offered TLS 1.3 and still received TLS 1.2 would know its offer was removed on the way, and "MUST abort the handshake with an "illegal_parameter" alert".

The mark cannot be rubbed out. In a TLS 1.2 handshake with an ephemeral key exchange, the server signs both random values together with its key share. munotes.in's signature verifies, and with the last eight bytes changed it fails, so an attacker who erased the mark would be caught by the signature check. RFC 9846 states the limit: the mark "does not provide downgrade protection when static RSA is used", because RSA key exchange carries no such signature, one more reason RFC 10015 now forbids it.

The run: TLS 1.3's key schedule

RFC 8448 publishes every secret of a real TLS 1.3 handshake, so that anyone can check an implementation against it. The second listing takes that handshake's client private key and server key share, computes the X25519 shared secret, and derives each secret in the order RFC 9846 gives, comparing each with the value RFC 8448 prints.

# ClientHello, RFC 8448 s.3
010000c00303cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912
ec18a2ef6283024dece7000006130113031302010000910000000b00
09000006736572766572ff01000100000a00140012001d0017001800
190100010101020103010400230000003300260024001d002099381d
e560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf
2c002b0003020304000d0020001e0403050306030203080408050806
04010501060102010402050206020202002d00020101001c00024001
# ServerHello, RFC 8448 s.3
020000560303a6af06a4121860dc5e6e60249cd34c95930c8ac5cb14
34dac155772ed3e2692800130100002e00330024001d0020c9828876
112095fe66762bdbf7c672e156d6cc253b833df1dd69b1b04e751f0f
002b00020304
munotes.in538

TLS, and What Each Version Fixed

# TLS 1.3's key schedule, reproduced from RFC 8448's published handshake, value by value.
import hashlib, hmac

P = 2**255 - 19
# ---- X25519, RFC 7748 s.5 -----------------------------------------------------------------
def x25519(k, u):
    k = int.from_bytes(k, 'little'); k &= ~7; k &= ~(128 << 8 * 31); k |= 64 << 8 * 31
    u = int.from_bytes(u, 'little') & ((1 << 255) - 1)
    x1, x2, z2, x3, z3, swap = u, 1, 0, u, 1, 0
    for t in reversed(range(255)):
        bit = (k >> t) & 1; swap ^= bit
        if swap: x2, x3, z2, z3 = x3, x2, z3, z2
        swap = bit
        A = x2 + z2; AA = A * A; B = x2 - z2; BB = B * B; E = AA - BB
        C = x3 + z3; Dd = x3 - z3; DA = Dd * A; CB = C * B
        x3 = (DA + CB) ** 2 % P; z3 = x1 * (DA - CB) ** 2 % P
        x2 = AA * BB % P; z2 = E * (AA + 121665 * E) % P
    if swap: x2, x3, z2, z3 = x3, x2, z3, z2
    return (x2 * pow(z2, P - 2, P) % P).to_bytes(32, 'little')

# ---- RFC 8448 s.3: the key schedule of a published TLS 1.3 handshake ---------------------
def hkdf_extract(salt, ikm):                       # RFC 5869, with SHA-256
    return hmac.new(salt, ikm, hashlib.sha256).digest()

def hkdf_expand_label(secret, label, context, length):   # RFC 9846 s.7.1 (RFC 8446 s.7.1)
    full = b'tls13 ' + label
    info = length.to_bytes(2, 'big') + bytes([len(full)]) + full + bytes([len(context)]) + context
    out, t, i = b'', b'', 1
    while len(out) < length:
        t = hmac.new(secret, t + info + bytes([i]), hashlib.sha256).digest()
        out, i = out + t, i + 1
    return out[:length]

def derive_secret(secret, label, messages):
    return hkdf_expand_label(secret, label, hashlib.sha256(messages).digest(), 32)

hellos = {}
for line in open('rfc8448-hellos.hex').read().split('\n'):
    if line.startswith('# '):
        name = line.split()[1].rstrip(',')
        hellos[name] = b''
    elif line:
        hellos[name] += bytes.fromhex(line)
transcript = hellos['ClientHello'] + hellos['ServerHello']

client_private = bytes.fromhex('49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005')
server_public = bytes.fromhex('c9828876112095fe66762bdbf7c672e156d6cc253b833df1dd69b1b04e751f0f')
PRINTED = {   # the values RFC 8448 s.3 prints (it calls the main secret "master")
    'shared': '8bd4054fb55b9d63fdfbacf9f04b9f0d35e6d63f537563efd46272900f89492d',
    'early': '33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a',
    'handshake': '1dc826e93606aa6fdc0aadc12f741b01046aa6b99f691ed221a9f0ca043fbeac',
    'c hs traffic': 'b3eddb126e067f35a780b3abf45e2d8f3b1a950738f52e9600746a0e27a55a21',
    's hs traffic': 'b67b7d690cc16c4e75e54213cb2d37b4e9c912bcded9105d42befd59d391ad38',
    'main': '18df06843d13a08bf2a449844c5f8a478001bc4d4c627984d5a41da8d0402919',
    'server key': '3fce516009c21727d0f2e4e86ee403bc',
    'server iv': '5d313eb2671276ee13000b30',
}
zero = bytes(32)
got = {}
got['shared'] = x25519(client_private, server_public)
got['early'] = hkdf_extract(zero, zero)
got['handshake'] = hkdf_extract(derive_secret(got['early'], b'derived', b''), got['shared'])
got['c hs traffic'] = derive_secret(got['handshake'], b'c hs traffic', transcript)
got['s hs traffic'] = derive_secret(got['handshake'], b's hs traffic', transcript)
got['main'] = hkdf_extract(derive_secret(got['handshake'], b'derived', b''), zero)
got['server key'] = hkdf_expand_label(got['s hs traffic'], b'key', b'', 16)
got['server iv'] = hkdf_expand_label(got['s hs traffic'], b'iv', b'', 12)
print('RFC 8448 s.3 REPRODUCED (%d-byte ClientHello, %d-byte ServerHello)'
      % (len(hellos['ClientHello']), len(hellos['ServerHello'])))
STEP = {'shared': 'X25519 of the client key and the server share',
        'early': 'HKDF-Extract(0, 0): no pre-shared key',
        'handshake': 'HKDF-Extract(Derive-Secret(early, "derived"), shared)',
        'c hs traffic': 'Derive-Secret(handshake, "c hs traffic", hellos)',
        's hs traffic': 'Derive-Secret(handshake, "s hs traffic", hellos)',
        'main': 'HKDF-Extract(Derive-Secret(handshake, "derived"), 0)',
        'server key': 'HKDF-Expand-Label(s hs traffic, "key", 16)',
        'server iv': 'HKDF-Expand-Label(s hs traffic, "iv", 12)'}
for name in PRINTED:
    print('  %-12s %-53s %s' % (name, STEP[name], got[name].hex() == PRINTED[name]))

# ---- the keys are bound to the hellos: change one bit of the ClientHello ------------------
altered = bytearray(transcript)
altered[100] ^= 1
assert bytes(altered) != transcript
key = hkdf_expand_label(derive_secret(got['handshake'], b's hs traffic', bytes(altered)),
                        b'key', b'', 16)
print()
print('ONE BIT OF THE CLIENTHELLO CHANGED')
print('  server handshake key  %s' % key.hex())
print('  the same as before:   %s' % (key == got['server key']))
munotes.in539

TLS, and What Each Version Fixed

RFC 8448 s.3 REPRODUCED (196-byte ClientHello, 90-byte ServerHello)
  shared       X25519 of the client key and the server share         True
  early        HKDF-Extract(0, 0): no pre-shared key                 True
  handshake    HKDF-Extract(Derive-Secret(early, "derived"), shared) True
  c hs traffic Derive-Secret(handshake, "c hs traffic", hellos)      True
  s hs traffic Derive-Secret(handshake, "s hs traffic", hellos)      True
  main         HKDF-Extract(Derive-Secret(handshake, "derived"), 0)  True
  server key   HKDF-Expand-Label(s hs traffic, "key", 16)            True
  server iv    HKDF-Expand-Label(s hs traffic, "iv", 12)             True

ONE BIT OF THE CLIENTHELLO CHANGED
  server handshake key  94712fcdb8f02c7e447fa656c5c1d2a4
  the same as before:   False

What the run establishes, in order.

HKDF replaces the PRF. TLS 1.2 expanded one master secret with its PRF, as the handshake chapter ran. TLS 1.3 builds a chain: an early secret, then a handshake secret that mixes in the key exchange's result, then the main secret, which RFC 8446 and RFC 8448 call the master secret. Each step is HKDF-Extract, and each key is HKDF-Expand-Label with a label of its own ("c hs traffic", "s hs traffic", "key", "iv"), so the client's keys and the server's, and the handshake's and the data's, are always different.

The hellos are hashed into the keys. The handshake secrets are derived from a hash of the ClientHello and the ServerHello. With one bit of the ClientHello changed, the server's handshake key is completely different, so an attacker who edits either hello leaves the two sides holding keys that do not match, and the handshake fails.

What is forbidden now

  • SSL 2.0. RFC 6176 (March 2011) requires that TLS clients and servers "never negotiate the use of SSL version 2.0".
  • SSL 3.0. RFC 7568 (June 2015): "SSLv3 MUST NOT be used", because it "is not sufficiently secure"; its CBC padding "trivially permits the recovery of plaintext", the POODLE attack.
  • TLS 1.0 and TLS 1.1. RFC 8996 (March 2021): "TLS 1.0 MUST NOT be used" and "TLS 1.1 MUST NOT be used". Among its reasons: "The integrity of the handshake depends on SHA-1 hash", and neither version supports AEAD ciphers. RFC 9846 (Appendix E.5) repeats it for all four old versions: "they MUST NOT be negotiated for any reason".
  • RSA key exchange, even in TLS 1.2. RFC 10015 (July 2026): "Clients MUST NOT offer and servers MUST NOT select RSA cipher suites in (D)TLS 1.2 connections". Finite-field Diffie-Hellman in TLS 1.2 is forbidden in the same words.
munotes.in540

TLS, and What Each Version Fixed

New names for old secrets. RFC 9846 (Appendix D) renames TLS 1.2's master secret the main secret and the pre-master secret the preliminary secret, keeping the label inside the PRF unchanged "for compatibility". An answer written from the syllabus's textbook uses master secret, and that is correct for the exam; knowing the new names shows the answer is current.

Distinctions that carry marks

TLS 1.2TLS 1.3
Round trips before datatwoone, or none for early data
Key exchangeRSA or Diffie-Hellman, ephemeral or notephemeral Diffie-Hellman or a hybrid with ML-KEM, always forward secret
Record protectionCBC with a MAC, or AEADAEAD only
A cipher suite namesthe key exchange, the signature, the cipher and the hash, which OpenSSL prints as ECDHE-ECDSA-AES256-GCM-SHA384the cipher and the hash only: TLS_AES_256_GCM_SHA384
Server certificatesent in the clearencrypted
Key derivationthe PRF, from a master secretthe HKDF key schedule
Where the version goesthe version field, 0x0303the supported_versions extension, 0x0304
Compression and ChangeCipherSpecpresentremoved
Marker in the server randomMeans
44 4F 57 4E 47 52 44 01 ("DOWNGRD" 01)a TLS 1.3 server negotiated TLS 1.2
44 4F 57 4E 47 52 44 00 ("DOWNGRD" 00)a server negotiated TLS 1.1 or below

What beginners get wrong here

Treating SSL and TLS as rival protocols. TLS is SSL continued under a new name by the IETF. Every version of SSL is now prohibited; people who say "SSL certificate" mean a certificate used with TLS.

Giving TLS 1.0 or 1.2 as the latest version. TLS 1.0 and 1.1 have been deprecated since 2021. The current version is TLS 1.3, specified since July 2026 by RFC 9846.

Reading a TLS 1.3 cipher suite as naming the key exchange. TLS_AES_256_GCM_SHA384 says nothing about how the keys were agreed; that is the key share group, X25519MLKEM768 for munotes.in.

Calling 0-RTT a free speed-up. Early data can be replayed by an attacker and is not forward secret.

Saying TLS always encrypts the certificate. Only TLS 1.3 does; TLS 1.2 sends it in the clear.

Thinking the downgrade mark is secret. It is a fixed, public value. It works because the server signs its random value, so the mark cannot be removed unnoticed.

munotes.in541

TLS, and What Each Version Fixed

Quick revision

  • TLS is the IETF's SSL: RFC 2246 (January 1999), version {3, 1} because it is a minor change to SSL 3.0.
  • SSL 3.0 to TLS 1.0: version, HMAC, the PRF, master secret, alerts (no_certificate out, 12 in), cipher suites (Fortezza out), certificate types, certificate_verify and finished (12 bytes from the PRF), padding (up to 255 bytes).
  • 1.1 (2006): explicit IV. 1.2 (2008): SHA-256 PRF, named hash in signatures, AEAD. 1.3 (2018, re-published as RFC 9846 in July 2026): one round trip, forward secrecy always, AEAD only, encrypted handshake, HKDF.
  • Forbidden: SSL 2.0 (RFC 6176), SSL 3.0 (RFC 7568), TLS 1.0 and 1.1 (RFC 8996), RSA and finite-field DH key exchange in TLS 1.2 (RFC 10015).
  • Downgrade mark: DOWNGRD with 01 (fell to 1.2) or 00 (fell to 1.1 or below), protected by the server's signature.
  • munotes.in, 30 September 2026: TLS 1.3, TLS_AES_256_GCM_SHA384, key exchange X25519MLKEM768.
  • TLS 1.2's master and pre-master secrets are called the main and preliminary secrets since RFC 9846.

Test yourself

1. What is TLS? State how TLS 1.0 differs from SSL 3.0. TLS is the IETF's standard version of SSL, first specified in RFC 2246 in 1999. TLS 1.0 differs from SSL 3.0 in the version number (3.1 against 3.0); the MAC, which is HMAC and also covers the version; the pseudorandom function, which expands secrets using P_MD5 and P_SHA-1 together and also computes the master secret; the alert codes, where no_certificate was removed and twelve were added; the cipher suites, where Fortezza was dropped; the client certificate types, which fell from seven to four; the certificate_verify message, which hashes only the handshake messages; the finished message, which is 12 bytes computed with the PRF; and the padding, which may be up to 255 bytes to hide message lengths.

2. Why does TLS 1.0 carry the version number 3.1? Because TLS 1.0 is a minor modification of SSL 3.0, whose version is 3.0. RFC 2246 calls the value 3.1 historical. TLS 1.1 and 1.2 continued the scheme as 3.2 and 3.3.

3. What did TLS 1.1 and TLS 1.2 each change? TLS 1.1 (RFC 4346, 2006) replaced the implicit CBC initialisation vector with an explicit one to protect against CBC attacks, and reported padding errors with bad_record_mac instead of decryption_failed. TLS 1.2 (RFC 5246, 2008) replaced the MD5 and SHA-1 combination in the PRF with a PRF chosen by the cipher suite, P_SHA256 in all its own suites, replaced the combination in signatures with a single named hash, and added authenticated encryption modes such as AES-GCM.

4. List the major differences between TLS 1.2 and TLS 1.3. TLS 1.3 completes the handshake in one round trip instead of two, and allows zero-round-trip early data on resumption; it removes static RSA and Diffie-Hellman, so every key exchange is forward secret; it keeps only AEAD ciphers and removes compression; it encrypts every handshake message after the ServerHello, including the certificate; it replaces the PRF with an HKDF key schedule; it moves version negotiation into the supported_versions extension and drops ChangeCipherSpec; and its cipher suites name only the AEAD cipher and hash.

munotes.in542

TLS, and What Each Version Fixed

5. Why can a TLS 1.3 handshake finish in one round trip? Because the client sends its Diffie-Hellman key share in the ClientHello, guessing the group the server will choose. The server can then compute the shared secret at once and send its own share, its encrypted certificate, CertificateVerify and Finished in a single reply. In TLS 1.2 the key exchange needed a second round trip after the hellos.

6. How does TLS 1.3 protect against an attacker forcing an older version? First, the hellos are hashed into the handshake keys, so any change to them makes the keys disagree and the handshake fail. Second, a TLS 1.3 server that negotiates TLS 1.2 must end its random value with the bytes DOWNGRD 01, or DOWNGRD 00 for an older version, and a client that offered TLS 1.3 must abort if it sees them. Because in TLS 1.2 the server signs its random value, the mark cannot be erased without the signature failing, as munotes.in's own handshake shows.

7. Which versions of SSL and TLS may no longer be used, and on whose authority? SSL 2.0 is prohibited by RFC 6176 (2011), SSL 3.0 by RFC 7568 (2015), and TLS 1.0 and 1.1 by RFC 8996 (2021); RFC 9846 (2026) repeats that none of the four may be negotiated for any reason. TLS 1.2 remains in use, but RFC 10015 (2026) forbids RSA and finite-field Diffie-Hellman key exchange within it.

Contents This chapter on its own page

munotes.in543

Chapter Eighty-One

Secure Electronic Transaction, and the Dual Signature

Syllabus topic Module 2, "Web Security: Secure Electronic Transaction"

In one line

SET was MasterCard and Visa's 1997 protocol for paying by card on the Internet: every party held a certificate, the card number went only to the bank's payment gateway, the order only to the merchant, and one dual signature by the cardholder tied the two together.

In the words an answer should use: Secure Electronic Transaction (SET) is an open specification, published by MasterCard and Visa (version 1.0, 31 May 1997), for securing payment card transactions over open networks such as the Internet. It provides confidentiality of payment information, integrity of all transmitted data, authentication of the cardholder as a legitimate user of the card account, and authentication of the merchant as able to accept card payments, using X.509 certificates, RSA, DES and SHA-1. Its best-known mechanism is the dual signature, which links the order information sent to the merchant with the payment information sent to the bank.

A protocol that is not in use

SET is not used today; it never took off. MU sets it, and it is worth learning for two reasons. The dual signature is a clean idea that answers a real problem, and it is examined. And the protocols that replaced SET gave up some of what it protected, which is a lesson in how security gets decided.

The participants

SET's own list, from Book 1 of the specification:

ParticipantWho it isWhat it does in SET
Cardholdera customer buying from a personal computerholds a card from an issuer; with SET, the card details stay hidden from the merchant
Merchanta seller of goods or servicesmust have a relationship with an acquirer to accept cards
Issuerthe cardholder's bankissues the card and guarantees payment for authorised transactions
Acquirerthe merchant's bankopens the merchant's account and processes card authorisations and payments
Payment gatewaya system run by the acquirer or a third partyprocesses the merchant's payment messages, including the cardholder's payment instructions
Certification authoritya trusted body under the card brandissues the certificates every party needs

"In a SET transaction, the electronic processing begins with the cardholder", Book 1 notes; in a shop or a mail order it begins with the merchant.

What SET set out to do

The specification names seven business requirements. In short:

  1. confidentiality of payment information, and of the order information sent with it;
  2. integrity of all transmitted data;
  3. authentication that the cardholder is a legitimate user of the card account;
  4. authentication that the merchant can accept card transactions through an acquirer;
  5. the best security practices and system design, to protect all legitimate parties;
  6. a protocol that "neither depends on transport security mechanisms nor prevents their use";
  7. interoperability among software and network providers.
munotes.in544

Secure Electronic Transaction, and the Dual Signature

It meets them with five features: confidentiality of information, integrity of data, cardholder account authentication, merchant authentication, and interoperability.

Certificates for everyone

Every party holds two key pairs, one for signatures and one for key exchange, and "Because SET participants have two key pairs, they also have two certificates". The certificates form a hierarchy of trust: a root certification authority certifies each card brand, the brand certifies (sometimes through a geopolitical authority) the authorities that certify cardholders, merchants and payment gateways, and the root's public key is built into every SET program.

A cardholder certificate does not contain the card number. It carries a one-way hash of the account number, the expiry date and a secret known only to the cardholder's software, so the certificate proves the link without revealing the number. The cardholder later reveals the number and the secret to the payment gateway, and nobody else.

The dual signature

The problem. The cardholder sends two things: the order information (OI), which the merchant needs, and the payment information (PI), with the card details, which only the bank needs. The merchant should not see the card number, and the bank has no need to see what was bought. Yet the two must be linked: the payment must be usable only for this order, and the order only with this payment.

The construction. The cardholder computes the message digest of each part, joins the two digests, hashes the result, and signs that once with the private signature key:

PIMD = H(PI)                    payment information message digest
OIMD = H(OI)                    order information message digest
POMD = H(PIMD || OIMD)          payment-order message digest
DS   = E(PRc, POMD)             the dual signature, with the cardholder's private key

How each side checks it. The merchant receives OI, PIMD and DS. It computes H(PIMD || H(OI)) and compares the result with D(PUc, DS), the signature opened with the cardholder's public key. The payment gateway receives PI, OIMD and DS, computes H(H(PI) || OIMD), and makes the same comparison. Each verifies the whole signature while seeing only its own half.

The formal version. Book 2 of the specification defines the signature over the two digests wrapped in PKCS #7 structures, and carries a salted hash of the order description rather than the description itself. The idea is exactly the one above.

The run: a dual signature with SET's own algorithms

The listing makes a cardholder signature key as SET specified it, RSA with a 1,024-bit modulus, and uses SHA-1 for every hash, as SET did. It signs an order and a payment once, lets the merchant and the gateway each check with only what they hold, and then tries three frauds.

munotes.in545

Secure Electronic Transaction, and the Dual Signature

# SET's dual signature, as the 1997 specification defines it: one signature that ties an order
# to a payment, checked by a merchant who never sees the card and a bank that never sees the order.
import hashlib, random

def H(data):                                   # SET Book 2: SHA-1 for every hash
    return hashlib.sha1(data).digest()

# ---- the cardholder's signature key: RSA with a 1,024-bit modulus (SET Book 2, Table 4) --------
rng = random.Random(1997)

def is_prime(n):
    d, s = n - 1, 0
    while d % 2 == 0:
        d, s = d // 2, s + 1
    for _ in range(40):                        # Miller-Rabin
        x = pow(rng.randrange(2, n - 1), d, n)
        if x in (1, n - 1):
            continue
        for _ in range(s - 1):
            x = x * x % n
            if x == n - 1:
                break
        else:
            return False
    return True

def prime(bits):
    while True:
        p = rng.getrandbits(bits) | (3 << bits - 2) | 1
        if is_prime(p):
            return p

e = 65537
while True:
    p, q = prime(512), prime(512)
    if p != q and (p - 1) * (q - 1) % e:
        break
n, d = p * q, pow(e, -1, (p - 1) * (q - 1))
K = n.bit_length() // 8                        # 128 bytes

DIGEST_INFO = bytes.fromhex('3021300906052b0e03021a05000414')   # SHA-1, RFC 8017 s.9.2

def block(digest):                             # EMSA-PKCS1-v1_5, the block RSA signs
    t = DIGEST_INFO + digest
    return b'\x00\x01' + b'\xff' * (K - len(t) - 3) + b'\x00' + t

def sign(digest):                              # "encrypting this digest with the private key"
    return pow(int.from_bytes(block(digest), 'big'), d, n).to_bytes(K, 'big')

def matches(digest, signature):                # "decrypting" with the public key, comparing
    return pow(int.from_bytes(signature, 'big'), e, n).to_bytes(K, 'big') == block(digest)

# ---- the cardholder builds the two messages and signs them ONCE ------------------------------
PI = b'transaction 4471 | card 4111 1111 1111 1111, expires 12/28 | pay Rs 1,200 to Campus Books'
OI = b'transaction 4471 | 2 notebooks and 1 geometry box | Rs 1,200'
PIMD, OIMD = H(PI), H(OI)
POMD = H(PIMD + OIMD)
DS = sign(POMD)
print('THE CARDHOLDER SIGNS ONCE')
print('  PIMD = H(PI)            %s' % PIMD.hex())
print('  OIMD = H(OI)            %s' % OIMD.hex())
print('  POMD = H(PIMD || OIMD)  %s' % POMD.hex())
print('  dual signature: %d bytes, one RSA operation with a %d-bit key' % (len(DS), n.bit_length()))

# ---- each party checks with only what it is given ---------------------------------------------
def merchant_check(oi, pimd, ds):              # the order, the PI's digest, the signature
    return matches(H(pimd + H(oi)), ds)

def gateway_check(pi, oimd, ds):               # the payment, the OI's digest, the signature
    return matches(H(H(pi) + oimd), ds)

print()
print('WHO SEES WHAT')
print('  merchant verifies, holding OI + PIMD + signature:  ', merchant_check(OI, PIMD, DS))
print('  card number among the merchant\'s bytes:            ', b'4111' in OI + PIMD + DS)
print('  gateway verifies, holding PI + OIMD + signature:   ', gateway_check(PI, OIMD, DS))
print('  the goods among the gateway\'s bytes:               ', b'notebooks' in PI + OIMD + DS)

# ---- what the link stops ----------------------------------------------------------------------
raised = OI.replace(b'Rs 1,200', b'Rs 12,000')
assert raised != OI
other_pi = PI.replace(b'4471', b'4472')
assert other_pi != PI
print()
print('WHAT THE LINK STOPS')
print('  order raised to Rs 12,000 by the merchant, gateway accepts:',
      gateway_check(PI, H(raised), DS))
print('  this order paired with another payment, gateway accepts:  ',
      gateway_check(other_pi, OIMD, DS))
print('  one character of the order changed, merchant accepts:     ',
      merchant_check(OI.replace(b'2 notebooks', b'3 notebooks'), PIMD, DS))
munotes.in546

Secure Electronic Transaction, and the Dual Signature

THE CARDHOLDER SIGNS ONCE
  PIMD = H(PI)            7d79ec190f4a2d5c43cde76549e41ae421a14ea5
  OIMD = H(OI)            d9e6fad217a038b09b2994bdeb9213b5b5defdea
  POMD = H(PIMD || OIMD)  a23cc08cc4bd83fa33a4af9560082b00d91fa1ed
  dual signature: 128 bytes, one RSA operation with a 1024-bit key

WHO SEES WHAT
  merchant verifies, holding OI + PIMD + signature:   True
  card number among the merchant's bytes:             False
  gateway verifies, holding PI + OIMD + signature:    True
  the goods among the gateway's bytes:                False

WHAT THE LINK STOPS
  order raised to Rs 12,000 by the merchant, gateway accepts: False
  this order paired with another payment, gateway accepts:   False
  one character of the order changed, merchant accepts:      False

What the run establishes, in order.

One signature serves two messages. The dual signature is 128 bytes, a single RSA operation over POMD.

Each party checks with its own half. The merchant verifies holding the order, the digest of the payment and the signature, and the card number is nowhere in those bytes. The gateway verifies holding the payment, the digest of the order and the signature, and the goods are nowhere in its bytes. The listing searches the bytes each party holds rather than taking this on trust.

The link holds. A merchant who raises the order to Rs 12,000 and sends the gateway the digest of the raised order is refused. So is a merchant who tries to pair this order with a different payment, and a single changed character in the order is caught by the merchant's own check. OpenSSL, checking the same key and signature independently, agreed.

SET's algorithms would not pass today. SHA-1 and a 1,024-bit RSA key were sound in 1997. NIST SP 800-131A (Rev. 2, March 2019) now lists RSA below 2,048 bits as "Disallowed" for generating signatures, and says SHA-1 "is disallowed for digital signature generation" outside specific protocols; DES, SET's default cipher, was withdrawn on 19 May 2005.

A purchase, step by step

Book 1 describes a purchase in three stages. The message names are Book 2's.

Purchase request (PInitReq, PInitRes, PReq, PRes).

  1. After shopping, the cardholder's software sends an initiate request, naming the card brand.
  2. The merchant assigns a transaction identifier and replies, signed, with its own certificate and the payment gateway's certificate.
  3. The cardholder's software verifies both certificates up to the root, then creates the OI and the PI, both carrying the transaction identifier, and computes the dual signature.
  4. It encrypts the PI with a random symmetric key, and puts that key and the card account details in a digital envelope under the gateway's public key-exchange key. It sends the OI and the enveloped PI to the merchant.
  5. The merchant verifies the cardholder's certificate and the dual signature on the OI, passes the PI on for authorisation, and returns a signed purchase response.
munotes.in547

Secure Electronic Transaction, and the Dual Signature

Payment authorisation (AuthReq, AuthRes).

  1. The merchant signs an authorisation request with the amount and the transaction identifier, encrypts it under a new symmetric key in an envelope for the gateway, and sends it with the cardholder's still-sealed PI.
  2. The gateway opens both envelopes, verifies the merchant's signature and the dual signature on the PI, checks that the two transaction identifiers match, and asks the issuer through the card network.
  3. The gateway returns a signed, enveloped authorisation response, with an optional capture token.

Payment capture (CapReq, CapRes).

  1. Later, the merchant sends a signed, enveloped capture request with the final amount and the capture token.
  2. The gateway sends a clearing request to the issuer and returns a signed capture response. The money moves.

The merchant never opens the PI. It forwards the envelope untouched, which is how the card number reaches the gateway through a merchant that cannot read it.

Why SET failed

SET asked a great deal of everyone. Every cardholder needed SET software and a certificate from the bank, obtained through a registration exchange of its own; every merchant needed certificates and a connection to a payment gateway; and every step of a purchase signs, verifies or opens an envelope. SSL, already built into every browser, asked the cardholder for nothing.

Ross Anderson's account in Security Engineering (2008) is that SSL "won out over a more complex and heavyweight protocol called SET, because it placed less of a burden on developers", and that SET "failed to take off as the designers didn't get the incentives right". Protocols like SET, which "would have required heavier investment by banks and merchants, were allowed to wither on the vine".

What replaced it

SSL, then TLS, to the merchant. The card number travels encrypted, but to the merchant or its payment provider, who can read it: exactly what SET was designed to avoid.

3-D Secure. Branded Verified by Visa and MasterCard SecureCode, it lets the card's issuer authenticate the cardholder during checkout, for example with a one-time password. EMVCo maintains the specifications of EMV 3-D Secure, which it says helps issuers and merchants "prevent card-not-present (CNP) fraud". Murdoch and Anderson (2010) judged that "3-D Secure has lousy technology, but got the economics right (at least for banks and merchants)", and noted that SET "arranged things so that the merchant and the bank each got only the transaction data they needed; the bank did not get a description of the goods". Under 3-D Secure, to show the cardholder what is being paid for, that description "must be sent to the issuer".

munotes.in548

Secure Electronic Transaction, and the Dual Signature

Tokenisation. EMVCo describes it as replacing "valuable card data with payment tokens", so that a stolen token is worth far less than a stolen card number.

In India today. The Reserve Bank of India's directions of 25 September 2025 require that "All digital payment transactions shall be authenticated by at least two distinct factors of authentication", and that for transactions other than card present, at least one factor be "dynamically created or proven", unique to that transaction. Payment providers had to comply by 1 April 2026. The RBI notes that the ecosystem "has primarily adopted SMS-based One Time Password (OTP)" as the additional factor, and if a loss arises from a transaction that did not comply, "the issuer shall compensate the customer for the loss in full without demur". For one-off online card payments to overseas merchants, card issuers must have a way to validate the payment in place by 1 October 2026.

Distinctions that carry marks

An ordinary signatureSET's dual signature
Signsone messagethe hash of two message digests joined
Verifier needsthe whole messageits own message and the other's digest
Purposeintegrity and origin of one messagethe same, plus a link between two messages meant for different parties
SSL or TLSSET
Protectsa connectiona payment
The merchant sees the card numberyesno, only the gateway does
Cardholder needsa browserSET software and a certificate
Proof that the cardholder agreed to this ordernonethe dual signature
In useeverywherenowhere

What beginners get wrong here

Calling the dual signature two signatures. It is one signature, over the hash of the two digests joined together; its whole point is that one private-key operation covers both messages.

Saying the merchant decrypts the payment information. The merchant forwards the PI in its digital envelope unopened; only the payment gateway holds the key that opens it.

Swapping the issuer and the acquirer. The issuer is the cardholder's bank; the acquirer is the merchant's bank, and the payment gateway works for the acquirer.

Describing SET as how online payments work today. It never took off. Today's card payments travel over TLS to the merchant or its payment provider, and in India they need two factors of authentication, most often an OTP.

munotes.in549

Secure Electronic Transaction, and the Dual Signature

Quick revision

  • SET: MasterCard and Visa, version 1.0, 31 May 1997; secures card payments over open networks; not in use.
  • Participants: cardholder, merchant, issuer (cardholder's bank), acquirer (merchant's bank), payment gateway, certification authority.
  • Requirements: confidentiality, integrity, cardholder authentication, merchant authentication, best practice, independent of transport security, interoperability. Features: the first four plus interoperability.
  • Two key pairs, two certificates each; a hierarchy of trust from a root; the cardholder certificate hides the card number.
  • Dual signature: PIMD = H(PI), OIMD = H(OI), POMD = H(PIMD || OIMD), DS = E(PRc, POMD). Merchant holds OI, PIMD, DS; gateway holds PI, OIMD, DS.
  • Stages: purchase request, payment authorisation, payment capture.
  • Algorithms: SHA-1, RSA 1,024 bits, DES. Failed: too heavy, wrong incentives. Replaced by: TLS, 3-D Secure, tokenisation; in India, two factors with one dynamic (RBI, 2025).

Test yourself

1. What is SET? State the business requirements it was designed to meet. SET, Secure Electronic Transaction, is an open specification published by MasterCard and Visa in 1997 for securing payment card transactions over open networks such as the Internet. It was designed to provide confidentiality of payment information and of the order information sent with it; integrity of all transmitted data; authentication that the cardholder is a legitimate user of the card account; authentication that the merchant can accept card transactions through an acquirer; use of the best security practices to protect all parties; a protocol that neither depends on nor prevents transport security; and interoperability among software and network providers.

2. Name the participants in a SET transaction and state the role of each. The cardholder, who buys using a card issued by an issuer; the merchant, who sells and must have an acquirer; the issuer, the cardholder's bank, which issues the card and guarantees payment for authorised transactions; the acquirer, the merchant's bank, which processes authorisations and payments; the payment gateway, operated by the acquirer or a third party, which processes the merchant's payment messages and the cardholder's payment instructions; and the certification authorities, which issue the certificates every party uses.

3. Explain the dual signature, with its construction and how the merchant and the bank verify it. The dual signature links the order information, meant for the merchant, with the payment information, meant for the bank, without either seeing the other's part. The cardholder computes PIMD = H(PI) and OIMD = H(OI), then POMD = H(PIMD || OIMD), and signs POMD with the private signature key to give DS. The merchant, holding OI, PIMD and DS, checks that H(PIMD || H(OI)) equals DS opened with the cardholder's public key. The payment gateway, holding PI, OIMD and DS, checks that H(H(PI) || OIMD) equals the same value. A changed order or a substituted payment makes the check fail.

munotes.in550

Secure Electronic Transaction, and the Dual Signature

4. Why can a merchant in SET not read the cardholder's card number? Because the card number travels only in the payment information, which the cardholder encrypts with a random symmetric key and whose key, with the account details, is sealed in a digital envelope under the payment gateway's public key-exchange key. The merchant receives only PIMD, the digest of the payment information, and forwards the envelope unopened. Even the cardholder's certificate carries only a one-way hash of the account number.

5. Describe the purchase request and payment authorisation in SET. In the purchase request, the cardholder sends an initiate request; the merchant replies with a transaction identifier and its own and the gateway's certificates; the cardholder verifies them, builds the OI and the PI with the transaction identifier, dual-signs them, envelopes the PI for the gateway, and sends both to the merchant, who verifies the dual signature on the OI and returns a signed purchase response. In authorisation, the merchant sends the gateway a signed and enveloped authorisation request together with the sealed PI; the gateway opens both, verifies the merchant's signature and the dual signature, checks that the transaction identifiers match, obtains the issuer's approval, and returns a signed, enveloped authorisation response with an optional capture token.

6. Why did SET fail, and what is used instead? SET required every cardholder to install SET software and obtain a certificate from the bank, every merchant to hold certificates and connect to a payment gateway, and many public-key operations for each purchase, while SSL, already in every browser, asked nothing of the cardholder. In Anderson's account SSL won because it placed less of a burden on developers, and SET failed because its designers did not get the incentives right. Instead, card numbers travel over TLS to the merchant, the issuer authenticates the cardholder with 3-D Secure, often with a one-time password, and card numbers are increasingly replaced by tokens; in India the RBI requires at least two factors of authentication, one of them dynamic.

Contents This chapter on its own page

munotes.in551

Chapter Eighty-Two

Intruders: Who They Are and What They Do

Syllabus topic Module 2, "Intruders"

In one line

An intruder is anyone who gets into a system, or further into it, than they are allowed; James Anderson's 1980 report sorted them into three kinds, the masquerader, the misfeasor and the clandestine user, in order of how hard each is to see in the logs.

In the words an answer should use: an intruder is a person who gains, or tries to gain, access to a system or its resources without authorisation, or who goes beyond the access they have been given. There are three classes. A masquerader is an individual who is not authorised to use the computer and who penetrates the system's access controls to exploit a legitimate user's account. A misfeasor is a legitimate user who accesses data, programs or resources for which that access is not authorised, or who is authorised but misuses those privileges. A clandestine user is an individual who seizes supervisory control of the system and uses it to evade auditing and access controls, or to suppress audit collection. The masquerader is likely to be an outsider, the misfeasor is generally an insider, and the clandestine user can be either.

Where the three classes come from

In 1980 James P. Anderson wrote a report, "Computer Security Threat Monitoring and Surveillance", on using audit records to catch misuse; NIST keeps it among its early computer security papers. Its first move was to sort attackers by two questions: is the person authorised to use the computer at all, and is the person authorised to use the particular data or program?

Not authorised to use the data or programAuthorised to use the data or program
Not authorised to use the computerCase A: external penetration(cannot arise)
Authorised to use the computerCase B: internal penetrationCase C: misfeasance

Inside the system, Anderson then named three classes of user, "in an order of increasing difficulty in detecting their activity through audit trail data": the masquerader, the legitimate user misusing access (misfeasance), and the clandestine user.

The three classes

The masquerader uses someone else's identity. Anderson's point is that "with possession of the proper user identifier and password, he is a legitimate user as far as the computer system is concerned". What gives a masquerader away is that the use is extra: the real owner keeps working as usual, so the logs show use at unusual times, unusual frequency, an unusual volume of data, or unusual patterns of reference to programs and data. Anderson allows the masquerader to be an insider who has learned a colleague's password; the textbooks' version is usually an outsider. Both are right: the defining act is using another person's account.

The misfeasor uses their own account for something they should not. Because the account is genuine and the times are normal, nothing unusual shows except the amount or the target: an office clerk who downloads the whole student database, or reads records their job never needs. Anderson is candid about the limit: misuse as small as printing "Snoopy calendars" or extracting two extra records "would not be detected under any circumstance".

munotes.in552

Intruders: Who They Are and What They Do

The clandestine user takes control of the system itself, becoming an administrator, and uses that control to switch off or edit the logs. As far as the audit trail is concerned, Anderson wrote, the clandestine user "is 'the little man who isn't there'". Such a user is caught, if at all, from outside the logs: by comparing the operating system with a known good copy, or noticing the machine busy when it should be idle.

MasqueraderMisfeasorClandestine user
Whose accountsomeone else'stheir ownany, with administrator control
Usuallyan outsideran insidereither
Shows in the logs asuse at odd times, extra usetoo much, or the wrong recordsnothing: the logs are switched off or edited
Hardest to detectleastmoremost

What an intruder does

The intruder's aim is to gain access to a system, or to increase the privileges they already have. Almost always the first thing needed is a secret that proves identity, usually a password. Some intrusions a college would recognise:

  • guessing or cracking the password of a teacher's account on the results portal;
  • sending a message that looks like it comes from the university, with a link to a copy of the login page;
  • sitting down at a laboratory computer someone left logged in;
  • copying a file of student records that an account can read but its owner has no reason to open;
  • exploiting a flaw in a web server to run commands, and from there reaching the database behind it;
  • after getting in, creating a new administrator account so as to return later, and deleting the log entries.

Intrusion techniques follow two routes to the password. It can be guessed: a default password never changed, a word from a dictionary, the user's name, date of birth or phone number. Or it can be captured: by a Trojan horse or keylogger, by watching the network, by a fake login page, or by stealing the file in which the system keeps its passwords. The next chapter attacks that file, and the one after it deals with choosing passwords that survive the attack.

It is a crime in India. Under the Information Technology Act, 2000, accessing a computer without the owner's permission falls under section 43(a), and doing it "dishonestly or fraudulently" is punishable under section 66 with imprisonment up to three years, a fine up to five lakh rupees, or both. A masquerader also commits identity theft under section 66C, which punishes whoever "fraudulently or dishonestly make use of the electronic signature, password or any other unique identification feature of any other person".

munotes.in553

Intruders: Who They Are and What They Do

The run: the base-rate problem

Suppose a detector could tell intrusions from normal work. How good must it be? The listing takes a college network that writes a million log records a day, twenty of them from two intrusions, and applies Bayes' theorem: of all the alarms the detector raises, what fraction are real?

# The base-rate problem: why a detector that is right 99 times in 100 can still be useless.
# Bayes' theorem with exact fractions, on a day's log records from a college network.
from fractions import Fraction

RECORDS = 1_000_000          # log records the network writes in a day
BAD = 20                     # of them, produced by intrusions: 2 intrusions of 10 records each
base_rate = Fraction(BAD, RECORDS)

def chance_alarm_is_real(detection, false_alarm):
    """P(intrusion | alarm), by Bayes' theorem."""
    true_alarms = detection * base_rate
    false_alarms = false_alarm * (1 - base_rate)
    return true_alarms / (true_alarms + false_alarms)

def pct(x, places):
    return '%.*f%%' % (places, float(x) * 100)

print('BASE RATE: %d intrusion records among %s, one in %s'
      % (BAD, format(RECORDS, ','), format(int(1 / base_rate), ',')))
print()
print('A DETECTOR THAT CATCHES EVERY INTRUSION (detection rate 100%)')
print('  false alarm rate   false alarms a day   real alarms a day   an alarm is real')
for rate in (Fraction(1, 100), Fraction(1, 1_000), Fraction(1, 10_000),
             Fraction(1, 100_000), Fraction(1, 1_000_000)):
    false_per_day = rate * (RECORDS - BAD)
    print('  %-16s %18s %19d %18s'
          % (pct(rate, 4), format(round(false_per_day), ','), BAD,
             pct(chance_alarm_is_real(1, rate), 1)))

# ---- the same arithmetic, asked the other way round ---------------------------------------
print()
wanted = Fraction(1, 2)
# chance = d.b / (d.b + f.(1 - b)) = 1/2  gives  f = d.b / (1 - b)
needed = base_rate / (1 - base_rate)
print('For half of all alarms to be real, the false alarm rate must be at most %s,'
      % pct(needed, 5))
print('about one false alarm in %s clean records.' % format(round(1 / needed), ','))
assert chance_alarm_is_real(1, needed) == wanted

# ---- and a detector that misses some intrusions is worse still -----------------------------
print()
print('THE SAME DETECTOR CATCHING ONLY 70% OF INTRUSIONS, false alarm rate 0.0010%')
print('  an alarm is real:', pct(chance_alarm_is_real(Fraction(7, 10), Fraction(1, 100_000)), 1))
print('  intrusion records missed a day:', round(Fraction(3, 10) * BAD))
BASE RATE: 20 intrusion records among 1,000,000, one in 50,000

A DETECTOR THAT CATCHES EVERY INTRUSION (detection rate 100%)
  false alarm rate   false alarms a day   real alarms a day   an alarm is real
  1.0000%                      10,000                  20               0.2%
  0.1000%                       1,000                  20               2.0%
  0.0100%                         100                  20              16.7%
  0.0010%                          10                  20              66.7%
  0.0001%                           1                  20              95.2%

For half of all alarms to be real, the false alarm rate must be at most 0.00200%,
about one false alarm in 49,999 clean records.

THE SAME DETECTOR CATCHING ONLY 70% OF INTRUSIONS, false alarm rate 0.0010%
  an alarm is real: 58.3%
  intrusion records missed a day: 6
munotes.in554

Intruders: Who They Are and What They Do

What the run establishes, in order.

A detector that is wrong on 1 per cent of normal records is useless here. It catches all 20 intrusion records, and buries them under 10,000 false alarms a day: only 0.2 per cent of its alarms are real. Nobody can investigate 10,000 alarms a day, so in practice nobody investigates any.

The false alarm rate, not the detection rate, is what matters. The alarms become mostly real only when false alarms are as rare as intrusions themselves. For half of all alarms to be real, the detector may raise no more than one false alarm in about 50,000 clean records.

Missing intrusions makes it worse, not better. A detector that catches only 70 per cent of intrusions, at the same very low false alarm rate, gives fewer real alarms, so a smaller share of its alarms is real, and six intrusion records a day go unseen.

This is the base-rate fallacy. When the thing being looked for is rare, even a small error rate on the common case swamps it. Stefan Axelsson made the argument for intrusion detection in 2000, in "The Base-Rate Fallacy and the Difficulty of Intrusion Detection". In 2022 a researcher at the U.S. Army Research Laboratory, Robert Erbacher, found the false alarm rates reported for machine-learning detectors "most still in the range of 10^-2", that is 1 per cent, "and the rare exception achieving 10^-3": far above what the arithmetic demands.

When an intrusion is found in India

CERT-In, the national agency for incident response under section 70B of the IT Act, issued directions on 28 April 2022. Service providers, intermediaries, data centres, companies and government organisations "shall mandatorily report cyber incidents" of the listed kinds to CERT-In "within 6 hours of noticing such incidents". They must also keep the logs of all their systems "for a rolling period of 180 days" within India, and synchronise their clocks with the national time servers, so that logs from different machines can be put in order. "Unauthorised access of IT systems/data" is on the list of incidents to be reported.

Distinctions that carry marks

IntruderMalicious software
Isa persona program
Gets in byguessing or stealing credentials, or exploiting a flawbeing run, often by a trick
Detected byintrusion detection, audit recordsantivirus, behaviour monitoring
munotes.in555

Intruders: Who They Are and What They Do

Detection rateFalse alarm rate
Meansof real intrusions, the share the detector flagsof normal activity, the share the detector wrongly flags
When intrusions are rarematters less than it seemsdecides whether the detector is usable

What beginners get wrong here

Saying every intruder is an outsider. The misfeasor is an insider by definition, and Anderson notes that internal penetration is often more frequent than external.

Treating the masquerader and the misfeasor as the same. The masquerader uses someone else's account; the misfeasor misuses their own.

Calling a 99 per cent accurate detector good. On a network where intrusions are one record in 50,000, a 1 per cent false alarm rate makes nearly every alarm false.

Forgetting that the clandestine user defeats the logs. Audit records cannot catch someone who controls the audit; that needs checks from outside, such as comparing system files with a trusted copy.

Quick revision

  • Intruder: gains access, or raises privileges, beyond what is allowed. Anderson's 1980 report sorted them.
  • Anderson's matrix: external penetration (not authorised on the computer), internal penetration (authorised on the computer, not the resource), misfeasance (authorised on both, misused).
  • Masquerader: someone else's account, likely an outsider, shows as extra or odd-hours use.
  • Misfeasor: own account misused, an insider, shows as too much or the wrong data.
  • Clandestine user: seizes supervisory control, evades or suppresses auditing, either insider or outsider; hardest to detect.
  • Intrusion techniques: guess the password or capture it (Trojan, sniffing, phishing, stolen password file).
  • Base-rate fallacy: 20 bad records in 1,000,000; at 1% false alarms, 0.2% of alarms are real. The false alarm rate decides usefulness.
  • India: IT Act s.43(a), s.66 (up to 3 years), s.66C identity theft; CERT-In: report within 6 hours, logs 180 days.

Test yourself

1. Name and define the three classes of intruder. A masquerader is an individual not authorised to use the computer who penetrates its access controls to exploit a legitimate user's account. A misfeasor is a legitimate user who accesses data, programs or resources they are not authorised for, or who is authorised but misuses those privileges. A clandestine user seizes supervisory control of the system and uses it to evade auditing and access controls or to suppress audit collection.

2. Which class of intruder is hardest to detect from audit records, and why? The clandestine user, because having supervisory control lets them operate below the level at which audit records are made, switch auditing off, or delete their own records, so the logs contain nothing to find. Anderson called such a user "the little man who isn't there"; detection needs evidence from outside the logs, such as comparing the operating system with a trusted copy.

munotes.in556

Intruders: Who They Are and What They Do

3. How would a masquerader and a misfeasor show up in audit records? A masquerader's use is extra to the real account holder's, so it appears as activity at unusual times, unusual frequency, unusual volume of data or unusual patterns of reference. A misfeasor works through their own account at normal times, so what stands out is the quantity of data transferred or computer time used, or access to records outside their normal work; small misuse may not be detectable at all.

4. What is the base-rate fallacy in intrusion detection? Illustrate with numbers. It is the mistake of judging a detector by its detection and error rates without regard to how rare intrusions are. If a network writes 1,000,000 records a day of which 20 come from intrusions, a detector that flags every intrusion but also 1 per cent of normal records raises about 10,000 false alarms and 20 true ones, so only 0.2 per cent of its alarms are real. For half its alarms to be real, the false alarm rate must fall to about one in 50,000.

5. What must an organisation in India do on discovering an intrusion? Under CERT-In's directions of 28 April 2022, issued under section 70B(6) of the IT Act, service providers, intermediaries, data centres, companies and government organisations must report the listed cyber incidents to CERT-In within 6 hours of noticing them. They must keep the logs of all their systems for a rolling 180 days within India and synchronise their clocks with the national time servers. The intruder may be liable under sections 43 and 66 of the IT Act, and, for using another person's password, under section 66C.

Contents This chapter on its own page

munotes.in557

Chapter Eighty-Three

Intrusion Techniques: Attacking the Password File

Syllabus topic Module 2, "Intrusion Techniques"

In one line

A system should never store passwords, only a salted, deliberately slow hash of each one; then a thief who steals the file must guess passwords one account at a time, paying a high price for every guess.

In the words an answer should use: most systems authenticate a user by a user ID and password. The system keeps a password file that holds, for each user, not the password but a hash of it, usually combined with a random salt. At login the system hashes the password typed, with the stored salt, and compares the result with the stored value. The main intrusion techniques against passwords are to guess them, trying default passwords, short passwords, dictionary words and personal information, or to capture them, with a Trojan horse, by tapping the line or by stealing the password file, and then to run the guesses offline against the stolen file.

How a system checks a password it does not keep

A system that stored passwords as typed would hand every password to anyone who read the file, including its own administrators and every backup. So it stores a one-way hash instead. At login it hashes what was typed and compares; it never needs the password itself, and nobody can run the hash backwards. Unix's designers went further and hoped that "neither the password file nor the password program itself needed to be protected against being read by anyone".

That hope did not survive offline guessing, and the hashes moved to a file only the administrator can read. On a Linux system it is /etc/shadow: each line holds a login name and the "encrypted password" (really a hash), and its manual page is blunt: "This file must not be readable by regular users if password security is to be maintained."

A current entry names its method in a prefix. Some from the crypt(5) manual:

PrefixMethodThe manual's verdict
(none)descrypt, the original DES method from Unix V7"it is feasible to discover any passphrase hashed with this method"
$6$sha512crypt, SHA-512 basedacceptable, but its default of 5,000 rounds "is too low for modern hardware"
$2b$bcrypt, based on Blowfishdeveloped for OpenBSD; handles at most 72 characters of a password
$y$yescrypt, based on scrypt"Recommended for new hashes"

The ways in

Guessing. An intruder tries the passwords people actually use: defaults never changed ("admin", "password"), every short string, dictionary words, and personal details: names, dates of birth, phone numbers, roll numbers, the name of a pet or a cricket team. Morris and Thompson's own list of "good" things for an attacker to try included the dictionary spelled backwards, first names, street names and city names, and telephone numbers.

munotes.in558

Intrusion Techniques: Attacking the Password File

Capturing. Or the intruder gets the password without guessing: a Trojan horse or keylogger that records it; watching an unencrypted network; a copy of the login page sent in a message; or someone watching the keyboard.

Online and offline. Guessing at the login page is online: slow, visible, and stopped by locking an account after a few failures, which NIST's current guidance requires ("Verifiers SHALL implement a rate-limiting mechanism"). Guessing against a stolen password file is offline: the attacker's own computer, no limit, no one watching. The whole defence of the password file is to make offline guessing expensive.

What Morris and Thompson found in 1979

In "Password Security: A Case History" (1979), Robert Morris and Ken Thompson of Bell Laboratories, where Unix was written, described the attacks on their own system's passwords and the fixes. When users chose freely, of 3,289 passwords gathered over time, 86 per cent fell into a small set of weak classes, and "The dictionary search alone, which required only five minutes to run, produced about one third of the passwords."

Their fixes are still the fixes.

  1. Slower encryption. The first scheme "was far too fast". They used DES, "extremely slow when implemented in software", and iterated it 25 times.
  2. Less predictable passwords. The program asked for a longer password if the one entered was short and all letters.
  3. Salted passwords. A 12-bit random number, the salt, was added to each password before hashing and stored beside the result. A single password was no harder to find, but "the work of testing a given character string against a large collection of encrypted passwords has been multiplied by 4,096". More important, "it becomes impractical to prepare an encrypted dictionary in advance", and nobody can tell whether a person used the same password on two systems.

The run: one stolen file, attacked three ways

The listing makes a password file for eight users, three of whom chose strong passwords and five of whom chose passwords that appear in the attacker's word list of thirty common passwords. It then attacks the file three times, counting every hash the attacker computes.

# A stolen password file, attacked three ways: stored as a plain hash, salted, and salted with a
# slow hash. The attacker's work is COUNTED, hash by hash, not timed.
import hashlib, random

USERS = {                      # the attacker never sees this column
    'asha': 'sunshine',     'bilal': 'X7#qLm2!vR9p',  'chetan': 'mumbai123',
    'deepa': 'sunshine',    'farhan': 'cricket',      'gauri': 'teal-kettle-monsoon-42',
    'irfan': 'iloveyou',    'jaya': 'Tq9$wd3@Lp0z',
}
WORDLIST = ['123456', 'password', '12345678', 'qwerty', 'abc123', 'iloveyou', 'admin',
            'welcome', 'monkey', 'dragon', 'football', 'cricket', 'princess', 'sunshine',
            'india123', 'mumbai123', 'pune@123', 'letmein', 'qwerty123', 'password1',
            'shadow', 'master', 'ganesh', 'krishna', 'superman', 'batman', 'hello123',
            'freedom', 'whatever', 'trustno1']
rng = random.Random(1979)

def sha256(data):
    return hashlib.sha256(data).digest()

# ---- 1. the file stores SHA-256(password) --------------------------------------------------
plain = {user: sha256(pw.encode()) for user, pw in USERS.items()}
work = 0
table = {}                                   # built once, BEFORE any file is stolen
for word in WORDLIST:
    table[sha256(word.encode())] = word
    work += 1
cracked = {user: table[h] for user, h in plain.items() if h in table}
print('1. UNSALTED: one hash per word, then a lookup for every account')
print('   hashes computed: %d; accounts cracked: %d of %d: %s'
      % (work, len(cracked), len(USERS), ', '.join(sorted(cracked))))
print('   asha and deepa have the same entry, visible without cracking anything:',
      plain['asha'] == plain['deepa'])

# ---- 2. the file stores a 16-byte random salt and SHA-256(salt + password) -----------------
salted = {}
for user, pw in USERS.items():
    salt = rng.randbytes(16)
    salted[user] = (salt, sha256(salt + pw.encode()))
work, cracked = 0, {}
for user, (salt, h) in salted.items():       # nothing could be computed in advance
    for word in WORDLIST:
        work += 1
        if sha256(salt + word.encode()) == h:
            cracked[user] = word
            break
print()
print('2. SALTED: every word must be hashed again with every account\'s salt')
print('   hashes computed: %d; accounts cracked: %d of %d: %s'
      % (work, len(cracked), len(USERS), ', '.join(sorted(cracked))))
print('   asha and deepa have the same entry:', salted['asha'][1] == salted['deepa'][1])
salted_work = work

# ---- 3. salted, and hashed SLOWLY: PBKDF2-HMAC-SHA256, 600,000 iterations ------------------
ITER = 600_000                                # OWASP's current figure for PBKDF2-HMAC-SHA256
slow = {}
for user, pw in USERS.items():
    salt = rng.randbytes(16)
    slow[user] = (salt, hashlib.pbkdf2_hmac('sha256', pw.encode(), salt, ITER))
salt, h = slow['asha']
guesses = 0
for word in WORDLIST:                         # attack ONE account for real
    guesses += 1
    if hashlib.pbkdf2_hmac('sha256', word.encode(), salt, ITER) == h:
        break
print()
print('3. SALTED AND SLOW: PBKDF2-HMAC-SHA256 with %s iterations' % format(ITER, ','))
print('   asha found after %d guesses, costing %s HMAC computations'
      % (guesses, format(guesses * ITER, ',')))
print('   part 2\'s %d guesses would cost %s HMAC computations, %s times as many'
      % (salted_work, format(salted_work * ITER, ','), format(ITER, ',')))
print('   the server pays %s once per login; the attacker pays it for every guess'
      % format(ITER, ','))
print('   stored: asha:$pbkdf2-sha256$%d$%s$%s...' % (ITER, salt.hex(), h.hex()[:12]))
munotes.in559

Intrusion Techniques: Attacking the Password File

1. UNSALTED: one hash per word, then a lookup for every account
   hashes computed: 30; accounts cracked: 5 of 8: asha, chetan, deepa, farhan, irfan
   asha and deepa have the same entry, visible without cracking anything: True

2. SALTED: every word must be hashed again with every account's salt
   hashes computed: 152; accounts cracked: 5 of 8: asha, chetan, deepa, farhan, irfan
   asha and deepa have the same entry: False

3. SALTED AND SLOW: PBKDF2-HMAC-SHA256 with 600,000 iterations
   asha found after 14 guesses, costing 8,400,000 HMAC computations
   part 2's 152 guesses would cost 91,200,000 HMAC computations, 600,000 times as many
   the server pays 600,000 once per login; the attacker pays it for every guess
   stored: asha:$pbkdf2-sha256$600000$36f1beec8351c2bedd9e30423ff6dcaa$345d6341d6b7...
munotes.in560

Intrusion Techniques: Attacking the Password File

What the run establishes, in order.

Unsalted, the whole file falls to thirty hashes. The attacker hashes each word once, possibly long before the theft, and looks every stolen entry up in that table. Five accounts fall. Worse, asha and deepa have identical entries, so anyone reading the file knows they share a password without cracking anything; crack one and the other falls with it.

Salted, the same five fall, but the work is per account. Each account has its own 16-byte salt, so each word must be hashed again for each account: 152 hashes here instead of 30, and no table made in advance helps. Identical passwords now have different entries. The salt does not save a weak password; it stops the attacker attacking everyone at once.

Salted and slow, each guess costs 600,000 times as much. With PBKDF2-HMAC-SHA256 at 600,000 iterations, the server spends that effort once per login, which a user does not notice. The attacker spends it on every guess: asha's password, fourteenth in the word list, cost 8,400,000 HMAC computations, and part 2's 152 guesses would cost 91,200,000. A slow hash is the point: it is the only measure that makes each offline guess expensive.

Nothing saves a password that is in the list. All three files gave up asha's password in the end. The strong passwords were never found, because they were not in the list: which is the subject of the next chapter.

What current guidance requires

NIST SP 800-63B-4 (July 2025). "Verifiers SHALL store passwords in a form that is resistant to offline attacks. Passwords SHALL be salted and hashed using a suitable password hashing scheme." The salt "SHALL be at least 32 bits in length", and the cost factor "SHOULD be as high as practical without negatively impacting verifier performance" and "SHOULD be increased over time". It also recommends a further keyed hash with a secret key held apart from the file, which OWASP calls a pepper, so that the stolen file alone is not enough.

OWASP's Password Storage Cheat Sheet gives the settings in its order of preference: Argon2id with at least 19 MiB of memory, 2 iterations and 1 degree of parallelism; if that is unavailable, scrypt; for legacy systems, bcrypt with a work factor of 10 or more; and where FIPS-140 compliance is required, PBKDF2 "with a work factor of 600,000 or more" and HMAC-SHA-256, as the run used.

Why memory matters. An attacker with graphics cards or custom chips can run a fast hash, or even PBKDF2, in thousands of copies at once. A memory-hard function such as Argon2 (RFC 9106, September 2021) or scrypt needs a large amount of memory for every guess, which is far more costly to multiply than arithmetic. RFC 9106's "FIRST RECOMMENDED option" is Argon2id with 2 GiB of memory, because it "maximizes adversarial costs on dedicated brute-force hardware"; OWASP's 19 MiB is a minimum, not a target.

munotes.in561

Intrusion Techniques: Attacking the Password File

Distinctions that carry marks

HashSaltPepper
Isa one-way function of the passworda random value, different for each accounta secret key, the same for all accounts
Storedin the password filein the password file, beside the hashapart from the file, ideally in a hardware module
Secretnonoyes
Defeatsreading passwords straight from the filetables made in advance, and attacking all accounts at oncean attacker who has only the file
Online guessingOffline guessing
Againstthe login pagea stolen password file
Limited byrate limiting and account lockoutonly the cost of the hash
Defencelimit attempts, block common passwordssalt, a slow hash, a pepper
AttackTriesSalt defeats it?
Brute forceevery possible string up to some lengthno, but a slow hash slows it
Dictionarylikely passwords from a listno, but a slow hash slows it
Precomputed tablehashes computed in advance, looked up after the theftyes

What beginners get wrong here

Saying passwords are encrypted. They are hashed; encryption can be reversed with the key, and a password file must not be reversible by anyone. The old word "encrypted password" survives in manual pages, but the thing stored is a hash.

Thinking the salt must be kept secret. The salt sits in the file in plain view; its job is to make every account's hash different. The secret addition is the pepper.

Believing a salt protects a weak password. It does not; an attacker who targets one account still finds "sunshine" quickly. The salt stops precomputation and bulk attack; only a strong password stops a determined guess.

Using SHA-256 alone for passwords. A fast hash is exactly wrong here: its speed is the attacker's speed. Passwords need a password hashing scheme with a cost factor.

Quick revision

  • Store a salted, slow hash, never the password. Linux: /etc/shadow, "must not be readable by regular users".
  • Techniques: guess (defaults, short strings, dictionary, personal details) or capture (Trojan horse, keylogger, sniffing, fake login page, stolen file).
  • Online guessing is stopped by rate limiting; offline guessing only by the cost of the hash.
  • Morris and Thompson, 1979: 86% weak; dictionary in five minutes found a third; DES 25 times; 12-bit salt, work multiplied by 4,096, no precomputed dictionary.
  • Run: unsalted 30 hashes crack 5 of 8; salted 152, same 5; PBKDF2 at 600,000 iterations makes every guess 600,000 times dearer.
  • SP 800-63B-4: salted and hashed, salt at least 32 bits, cost as high as practical, a pepper recommended.
  • OWASP: Argon2id (19 MiB, t = 2, p = 1), scrypt, bcrypt (10+), PBKDF2-HMAC-SHA256 (600,000).
munotes.in562

Intrusion Techniques: Attacking the Password File

Test yourself

1. Explain how a system verifies a password without storing it. When the password is set, the system generates a random salt, computes a one-way hash of the salt and password with a deliberately slow password hashing scheme, and stores the salt, the hash and the scheme's parameters in the password file. At login it takes the password typed, hashes it with the stored salt and parameters, and compares the result with the stored hash; a match means the password was correct. The password itself is never stored and cannot be recovered from the hash.

2. List the techniques an intruder uses to learn a password. Trying default passwords; trying all short passwords; trying words from dictionaries and lists of common passwords; trying personal information such as names, dates of birth, phone numbers and roll numbers; using a Trojan horse or keylogger to record passwords; tapping or sniffing the line between user and system; tricking users with a copy of the login page; and stealing the password file to guess against it offline.

3. What is a salt, and what does it achieve? A salt is a random value generated for each password, combined with the password before hashing and stored in the clear beside the hash. It means that identical passwords give different stored values, that a table of hashes computed in advance is useless, and that an attacker must repeat every guess for every account; Morris and Thompson's 12-bit salt multiplied the work of attacking a whole file by 4,096. It does not make an individual weak password harder to guess.

4. Why should a password hash be slow, when most hashes are designed to be fast? Because the attacker who steals the file can make guesses as fast as the hash can be computed, and a fast hash lets them try billions. A password hashing scheme with a cost factor, such as PBKDF2 with 600,000 iterations or Argon2id, makes every guess expensive; the legitimate server pays the cost once per login, which a user does not notice, while the attacker pays it for every guess. A memory-hard scheme such as Argon2 also resists attackers who use many graphics cards or custom chips.

5. What does NIST SP 800-63B-4 require for storing passwords? That passwords be stored in a form resistant to offline attack: salted and hashed with a suitable password hashing scheme whose cost factor is as high as practical and raised over time, with a salt of at least 32 bits chosen to avoid collisions, and with the salt, the hash and the scheme's parameters stored for each password. It also recommends an extra keyed hash with a secret key kept separately from the password file.

Contents This chapter on its own page

munotes.in563

Chapter Eighty-Four

Password Selection and Management

Syllabus topic Module 2, "Intrusion Techniques"

In one line

A good password is one an attacker's list does not contain: long, not a known or predictable choice, and never reused; the four ways a system helps people choose one are education, generated passwords, checking afterwards, and checking at the moment of choice.

In the words an answer should use: the goal of password selection is to eliminate guessable passwords while allowing users to choose passwords they can remember. Four basic techniques are used: user education, telling users why hard-to-guess passwords matter and how to choose them; computer-generated passwords, chosen at random by the system; reactive password checking, in which the system periodically runs its own password cracker to find guessable passwords and cancels them; and proactive password checking, in which the system checks each password when the user chooses it and refuses a weak one.

Why people choose badly

Morris and Thompson found in 1979 that when users chose freely, 86 per cent of 3,289 passwords fell into a few easily searched classes. Nothing has improved it since: people remember what is familiar, and what is familiar is guessable. Keystroke loggers and fake login pages defeat any password, long or short; NIST notes that such attacks "are equally effective on lengthy and complex passwords as they are on simple ones". Choosing well protects against guessing, which is the attack this chapter is about.

The four strategies

1. User education. Explain the risk and how to choose well. It is cheap and necessary, and NIST's guidance requires it: verifiers "SHALL offer guidance to the subscriber to help the subscriber choose a strong password". On its own it fails, because many users ignore it or cannot judge what is guessable.

2. Computer-generated passwords. The system picks the password at random, so nothing about the user makes it guessable. Random strings are hard to remember and so get written down, and the generator itself must be good. Morris and Thompson tell of an installation whose system-chosen passwords were 8 characters from 36, which would have taken 112 years to search, but which came from a generator with only 2^15 starting values: an attacker tried them all "using a total of only about one minute of machine time". Today the useful form is a password manager that generates and remembers long random passwords, which NIST says verifiers "SHALL allow".

3. Reactive password checking. The system runs its own password cracker against its own password file from time to time, and cancels or flags any password it finds. It catches weak passwords, but only after they have been in use, possibly for a long time; the cracking takes much computing; and anyone who can run the checker needs the password hashes.

munotes.in564

Password Selection and Management

4. Proactive password checking. The system checks each password at the moment the user chooses it, and refuses a weak one. This is the approach that has lasted. Its forms have been simple rules, comparison with a dictionary of bad passwords, and, to hold a large dictionary in a small space, a Bloom filter, as in Eugene Spafford's OPUS system at Purdue. Today's form is NIST's blocklist: verifiers "SHALL compare the prospective secret against a blocklist that contains known commonly used, expected, or compromised passwords", and "The entire password SHALL be subject to comparison, not substrings or words that might be contained therein."

The run: the arithmetic, a Bloom filter, and two rulebooks

The listing works out how large the search is for passwords chosen at random, reproduces the Morris and Thompson generator story in numbers, sizes and builds a Bloom filter as Spafford did, and applies an old-style composition rule and NIST's current rule to the same four passwords.

# Choosing passwords: the arithmetic of guessing, a proactive checker built as a Bloom filter
# (Spafford's OPUS), and the old composition rule set beside NIST's current one.
import hashlib, math, random

# ---- 1. how many guesses a RANDOMLY chosen password needs -----------------------------------
print('1. RANDOM PASSWORDS: the size of the space an attacker must search')
for label, space in [('8 lower-case letters', 26 ** 8),
                     ('8 of letters and digits', 62 ** 8),
                     ('8 of all 94 printable characters', 94 ** 8),
                     ('15 lower-case letters', 26 ** 15),
                     ('4 words from a 7,776-word list', 7776 ** 4),
                     ('6 words from a 7,776-word list', 7776 ** 6)]:
    print('   %-34s %5.1f bits   about 10^%d guesses' % (label, math.log2(space),
                                                         len(str(space)) - 1))
print('   one word from 7,776 is worth %.1f bits' % math.log2(7776))

# ---- 2. Morris and Thompson's machine-chosen passwords: 36^8 on paper, 2^15 in fact ----------
print()
print('2. A GENERATOR IS ONLY AS STRONG AS ITS SEED')
print('   8 characters from 36: %s passwords, %.1f bits'
      % (format(36 ** 8, ','), math.log2(36 ** 8)))
print('   a generator with 2^15 starting values: %s passwords, 15.0 bits' % format(2 ** 15, ','))
print('   the attacker\'s work cut by a factor of %s' % format(36 ** 8 // 2 ** 15, ','))

# ---- 3. a proactive checker: OPUS's Bloom filter ---------------------------------------------
def bits_needed(n, d, p):
    """Spafford's formulas: unset share phi = (1 - d/N)^n, false positives P = (1 - phi)^d."""
    phi = 1 - p ** (1 / d)
    return d / (1 - phi ** (1 / n)), phi

print()
print('3. OPUS SIZED AS SPAFFORD SIZED IT (250,000 words, 6 hash functions)')
for p in (0.005, 0.01):
    size, phi = bits_needed(250_000, 6, p)
    print('   false positives %.1f%%: %s bits, %s bytes, unset share %.3f'
          % (p * 100, format(round(size), ','), format(round(size / 8), ','), phi))

BASE = ['password', 'sunshine', 'cricket', 'mumbai', 'india', 'welcome', 'dragon', 'monkey',
        'qwerty', 'iloveyou', 'princess', 'football', 'shadow', 'master', 'superman', 'krishna']
TAILS = ['', '1', '12', '123', '1234', '!', '1!', '@123', '#1', '007',
         '2024', '2025', '2026', '@2026', '99', '786']
forbidden = {case(word) + tail for word in BASE for tail in TAILS
             for case in (str.lower, str.capitalize, str.upper)}
n, d = len(forbidden), 7
N = round(-n * d / math.log(1 - 0.01 ** (1 / d)))    # sized for 1% false positives
bloom = bytearray((N + 7) // 8)

def spots(word):
    for i in range(d):
        h = hashlib.sha256(bytes([i]) + word.encode()).digest()
        yield int.from_bytes(h[:8], 'big') % N

for word in forbidden:
    for s in spots(word):
        bloom[s // 8] |= 1 << (s % 8)

def rejected(word):
    return all(bloom[s // 8] >> (s % 8) & 1 for s in spots(word))

rng = random.Random(1992)
alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'
trials, wrong = 100_000, 0
for _ in range(trials):
    w = ''.join(rng.choice(alphabet) for _ in range(10))
    wrong += w not in forbidden and rejected(w)
print('   a filter of %s forbidden passwords in %s bits (%s bytes), %d hash functions'
      % (format(n, ','), format(N, ','), format(len(bloom), ','), d))
print('   every forbidden password rejected:', all(rejected(w) for w in forbidden))
print('   harmless strings wrongly rejected: %d of %s, %.2f%%'
      % (wrong, format(trials, ','), 100 * wrong / trials))

# ---- 4. the old composition rule against NIST SP 800-63B-4 ------------------------------------
def old_rule(pw):
    return (len(pw) >= 8 and any(c.isupper() for c in pw) and any(c.islower() for c in pw)
            and any(c.isdigit() for c in pw) and any(not c.isalnum() for c in pw))

def nist_rule(pw):           # single-factor: 15 characters at least, and not on the blocklist
    return len(pw) >= 15 and not rejected(pw)

print()
print('4. %-28s %-18s %s' % ('PASSWORD', 'OLD RULE', 'SP 800-63B-4'))
for pw in ['Password1!', 'Sunshine@2026', 'Tq9$wd3@Lp0z', 'teal kettle monsoon ledger']:
    print('   %-28s %-18s %s' % (pw, 'accepted' if old_rule(pw) else 'refused',
                                  'accepted' if nist_rule(pw) else 'refused'))
munotes.in565

Password Selection and Management

1. RANDOM PASSWORDS: the size of the space an attacker must search
   8 lower-case letters                37.6 bits   about 10^11 guesses
   8 of letters and digits             47.6 bits   about 10^14 guesses
   8 of all 94 printable characters    52.4 bits   about 10^15 guesses
   15 lower-case letters               70.5 bits   about 10^21 guesses
   4 words from a 7,776-word list      51.7 bits   about 10^15 guesses
   6 words from a 7,776-word list      77.5 bits   about 10^23 guesses
   one word from 7,776 is worth 12.9 bits

2. A GENERATOR IS ONLY AS STRONG AS ITS SEED
   8 characters from 36: 2,821,109,907,456 passwords, 41.4 bits
   a generator with 2^15 starting values: 32,768 passwords, 15.0 bits
   the attacker's work cut by a factor of 86,093,442

3. OPUS SIZED AS SPAFFORD SIZED IT (250,000 words, 6 hash functions)
   false positives 0.5%: 2,811,022 bits, 351,378 bytes, unset share 0.586
   false positives 1.0%: 2,404,167 bits, 300,521 bytes, unset share 0.536
   a filter of 768 forbidden passwords in 7,367 bits (921 bytes), 7 hash functions
   every forbidden password rejected: True
   harmless strings wrongly rejected: 1071 of 100,000, 1.07%

4. PASSWORD                     OLD RULE           SP 800-63B-4
   Password1!                   accepted           refused
   Sunshine@2026                accepted           refused
   Tq9$wd3@Lp0z                 accepted           refused
   teal kettle monsoon ledger   refused            accepted
munotes.in566

Password Selection and Management

What the run establishes, in order.

Length does more than variety. Eight random characters from all 94 printable characters give 52.4 bits; fifteen random lower-case letters give 70.5. Six words picked at random from a 7,776-word list give 77.5 bits, which EFF's 2016 word list describes as "about 12.9 bits" a word. These figures hold only for random choices. A password a person thinks up has far less strength than its length suggests, which is why NIST now judges passwords "based primarily on password length" and a blocklist, rather than an entropy estimate.

A generator is only as strong as its seed. The 36^8 passwords of Morris and Thompson's installation looked like 41.4 bits; the generator could produce only 32,768 of them, 15 bits, and cut the attacker's work by a factor of 86 million.

A Bloom filter holds a large list in a small space, and never lets a listed password through. Spafford's formulas give, for 250,000 words and six hash functions, 2,811,022 bits, 351,378 bytes at a 0.5 per cent false-positive rate, and 300,521 bytes at 1 per cent: his report printed "2,800,000 bits", "350K bytes" and "300K bytes". The filter built here holds 768 forbidden passwords in 921 bytes and rejects every one of them. Its only error is the other way: about 1 in 100 harmless strings is wrongly refused, 1.07 per cent against the 1 per cent it was designed for, and the user simply picks another.

Composition rules reward the wrong passwords. "Password1!" and "Sunshine@2026" satisfy the old rule of upper case, lower case, digit and symbol, and are exactly what attackers try first; NIST's appendix uses "Password1!" as its own example. A random 12-character string also passes the old rule but is too short for NIST's single-factor minimum of 15. A four-word phrase of lower-case words fails the old rule and passes NIST's.

What current guidance says

NIST SP 800-63B-4 (July 2025) reverses several rules that most notes still teach:

RuleWhat many notes teachNIST SP 800-63B-4
Minimum length8 characters15 when the password is the only factor; 8 when part of multi-factor authentication
Maximum lengthoften 16 or lessshould permit at least 64
Compositionrequire upper case, digits and symbols"SHALL NOT impose other composition rules"
Periodic changeevery 30, 60 or 90 days"SHALL NOT require subscribers to change passwords periodically", but SHALL force a change on evidence of compromise
Blocklistrarely mentionedSHALL check against common, expected and compromised passwords
Hints and security questionscommonSHALL NOT
Password managers and pastesometimes blockedSHALL allow managers and autofill; SHOULD allow paste
Failed attemptsvariesSHALL rate-limit
munotes.in567

Password Selection and Management

Why forced expiry was dropped. NIST's own FAQ explains that users forced to change passwords "often select a secret that is similar to their old memorized secret by applying a set of common transformations such as increasing a number in the password", "Sunshine@2025" becoming "Sunshine@2026", and attackers apply the same transformations. Forcing a change when there is evidence of compromise protects; forcing it by the calendar mostly produces patterns.

Management, as well as selection

  • Never reuse a password across sites: a breach of one then opens all the others. A password manager makes unique passwords practical.
  • Change a password when it may have been exposed, not by the calendar.
  • Use a second factor where it is offered; a stolen password alone then does not open the account.
  • Never share a password, even with a friend or classmate; using it would be using another person's password, which is what section 66C of the IT Act punishes.

Distinctions that carry marks

Reactive checkingProactive checking
Whenperiodically, after passwords are setat the moment of choosing
Howthe system cracks its own filethe new password is compared with a list or rules
Weak passwords existuntil the next checknever
Costheavy computing, repeatedone check per change
Composition ruleBlocklist
Refusespasswords lacking certain character typesknown, common or breached passwords
Accepts "Password1!"yesno
NIST SP 800-63B-4forbiddenrequired

What beginners get wrong here

Equating complexity with strength. Symbols and capitals in predictable places add little; attackers' lists already contain "Password1!". Length and not being on a list matter more.

Calculating a person's password strength as if it were random. 94^8 describes a random string, not one a person made up; a human choice of 8 characters is usually far weaker.

Recommending a change every 90 days. Current NIST guidance forbids periodic forced changes and requires a change only when there is evidence of compromise.

Thinking a Bloom filter may let a weak password through. It never misses a listed password; its only error is occasionally refusing a good one.

Quick revision

  • Four strategies: user education, computer-generated passwords, reactive checking (crack your own file), proactive checking (check at the moment of choice).
  • Generated passwords are only as strong as the generator: Morris and Thompson's 36^8 was really 2^15.
  • Bloom filter (Spafford's OPUS): d hash functions set bits; a word is refused if all its bits are set; no false negatives, a small rate of false positives. 250,000 words, 6 hashes, 0.5% needs about 350 KB.
  • Random passwords: 8 of 94 is 52.4 bits; 6 words of 7,776 is 77.5 bits. Human choices are far weaker.
  • SP 800-63B-4: at least 15 characters single-factor (8 with MFA), allow 64, no composition rules, no periodic changes, blocklist, no hints or security questions, allow password managers, rate-limit attempts.
munotes.in568

Password Selection and Management

Test yourself

1. Describe the four basic techniques of password selection. User education tells users why guessable passwords are dangerous and how to choose good ones, but depends on users following it. Computer-generated passwords are chosen at random by the system, which removes guessable patterns but makes them hard to remember and depends on a good random generator. Reactive password checking has the system periodically run a password cracker on its own file and cancel any password it finds, but weak passwords stay in use until the next run and the checking is costly. Proactive password checking tests each password when the user chooses it and refuses weak ones, using rules, a dictionary of bad passwords or a compact structure such as a Bloom filter.

2. How does a Bloom filter serve as a proactive password checker? The filter is an array of N bits, all zero at first, and d independent hash functions each map a word to a position in the array. Every word in the dictionary of bad passwords is hashed by all d functions and the d positions are set to 1. When a user proposes a password, it is hashed the same way; if all d positions are 1 it is refused as probably in the dictionary, and if any is 0 it is certainly not in the dictionary and is accepted. A listed password is never accepted; a small, controllable fraction of good passwords is wrongly refused, and the filter is far smaller than the dictionary itself.

3. Why does current NIST guidance forbid composition rules and periodic password changes? Because users respond to composition rules in predictable ways, turning "password" into "Password1!", which attackers try first, while the rules make passwords harder to remember and more likely to be written down. Periodic forced changes produce predictable variations of the old password. NIST SP 800-63B-4 therefore requires a minimum length, a check against a blocklist of common and compromised passwords, and a forced change only when there is evidence of compromise.

4. Compare the strength of an eight-character random password with a six-word random passphrase. Eight characters chosen at random from the 94 printable characters give 94^8 possibilities, about 52.4 bits. Six words chosen at random from a list of 7,776 give 7,776^6 possibilities, about 77.5 bits, some 25 bits or about 36 million times more, and are easier to remember. Both figures apply only if the choice is truly random.

munotes.in569

Password Selection and Management

5. What rules for passwords does NIST SP 800-63B-4 lay down? Passwords used as the only factor must be at least 15 characters, and at least 8 when part of multi-factor authentication; at least 64 characters should be allowed, with all printing characters, spaces and Unicode. No other composition rules may be imposed and periodic changes may not be required, but a change must be forced on evidence of compromise. Every new password must be checked in full against a blocklist of common, expected and compromised passwords; hints and security questions are not allowed; password managers and autofill must be allowed; and failed attempts must be rate-limited.

Contents This chapter on its own page

munotes.in570

Chapter Eighty-Five

Intrusion Detection: Statistical Anomaly and Rule-Based

Syllabus topic Module 2, "Intrusion Detection"

In one line

Intrusion detection watches what happens on a system and looks for signs of attack in two ways: by comparing behaviour with what is normal for that user or system (statistical anomaly detection), or by matching it against rules and patterns that describe misuse and known attacks (rule-based detection).

In the words an answer should use: intrusion detection is based on the assumption that the behaviour of an intruder differs from that of a legitimate user in ways that can be measured, although the two overlap. Statistical anomaly detection collects data on the behaviour of legitimate users over a period and applies statistical tests to decide whether new behaviour is legitimate; it comes as threshold detection, with thresholds on the frequency of events independent of the user, and profile-based detection, with a profile of activity for each user. Rule-based detection defines a set of rules that decide whether behaviour is that of an intruder; it comes as rule-based anomaly detection, rules drawn from past usage patterns, and rule-based penetration identification, rules that describe known attacks and suspicious behaviour.

What intrusion detection is for

NIST's guide, SP 800-94 (2007), defines it as "the process of monitoring the events occurring in a computer system or network and analyzing them for signs of possible incidents". Prevention fails sometimes, so detection matters for three reasons. An intrusion found quickly can be stopped before much damage is done. A system known to be watched deters some intruders. And what detection records shows how the intruder got in, which is how prevention improves.

Intrusion prevention goes one step further: it is "the process of performing intrusion detection and attempting to stop detected possible incidents", for example by dropping the attacker's traffic.

Statistical anomaly detection

Threshold detection counts events of a type over a period, such as failed logins in a minute, and raises an alarm above a fixed limit, whoever the user is. Dorothy Denning's 1987 paper "An Intrusion-Detection Model", where this approach was set out, calls it the operational model and gives the example of password failures "where more than 10, say, suggests an attempted break-in". It is simple and crude: a limit right for one user is wrong for another.

Profile-based detection builds a profile of each user's normal behaviour from their own past records, and flags departures from it. Denning's profiles are built from three kinds of metric:

MetricMeasuresExample from Denning
Event counterhow many audit records of a kind occur in a periodnumber of logins in an hour; password failures in a minute
Interval timerthe time between two related eventstime between successive logins to an account
Resource measurethe quantity of a resource used by an actionpages printed a day; CPU time used by a program
munotes.in571

Intrusion Detection: Statistical Anomaly and Rule-Based

Textbooks add a fourth, the gauge, a value that can go up as well as down, such as the number of connections a program has open.

To decide whether a new value is abnormal, Denning describes five statistical models:

  1. Operational: compare with fixed limits, as above.
  2. Mean and standard deviation: a value is abnormal if it lies more than d standard deviations from the mean. Whatever the data's shape, Chebyshev's inequality says at most 1/d^2 of normal values can fall outside; Denning's example is d = 4, "at most .0625".
  3. Multivariate: the same, on correlations between two or more metrics, such as CPU time against input and output.
  4. Markov process: treats each kind of event as a state and learns how often one follows another; a sequence of commands whose transitions are improbable is abnormal.
  5. Time series: uses the order and timing of values as well, and so can see a gradual shift in behaviour.

Its strength and its weaknesses. Anomaly detection can catch what nobody has seen before: SP 800-94 says it "can be very effective at detecting previously unknown threats". But the profile may be learned while an attacker is already present, and an attacker can "perform small amounts of malicious activity occasionally, then slowly increase the frequency" until a profile that keeps adjusting accepts it as normal.

Rule-based detection

Rule-based anomaly detection writes the past as rules instead of statistics: rules generated from historical audit records describe each user's usual patterns, and current behaviour that matches no rule is flagged.

Rule-based penetration identification writes rules about attacks instead: an expert system with rules describing known penetrations, known weaknesses and suspicious behaviour, such as a user reading files in other users' home directories, or a program that a user has never run appearing in their session. SP 800-94 calls such patterns signatures: "A signature is a pattern that corresponds to a known threat." Signature-based detection "is very effective at detecting known threats but largely ineffective at detecting previously unknown threats", and at variants of known ones.

The run: a profile, a limit, signatures, and a threshold

The listing builds one clerk's profile from 60 past sessions (synthetic data, made up for the illustration), applies Denning's mean and standard deviation model and operational model, runs three signatures taken from SP 800-94's own examples, and then moves the threshold of the anomaly detector to show what every choice costs.

# Intrusion detection two ways: statistical anomaly detection on a user's profile (Denning, 1987),
# and signatures (NIST SP 800-94's own examples). Then the trade-off every detector must choose.
import math, random

rng = random.Random(1987)

# ---- 1. a profile: records a clerk reads per session, over 60 past sessions -----------------
history = [max(0, round(rng.gauss(40, 8))) for _ in range(60)]
n = len(history)
total, squares = sum(history), sum(x * x for x in history)
mean = total / n
stdev = math.sqrt((squares - n * mean * mean) / (n - 1))
D = 4                                        # Denning's example: 4 standard deviations
low, high = mean - D * stdev, mean + D * stdev
print('1. MEAN AND STANDARD DEVIATION MODEL (60 past sessions)')
print('   mean %.1f records, standard deviation %.1f' % (mean, stdev))
print('   normal range at d = %d: %.1f to %.1f records' % (D, low, high))
print('   Chebyshev: at most %.2f%% of honest sessions can fall outside it' % (100 / D ** 2))
for x in (44, 71, 400):
    verdict = 'normal' if low <= x <= high else 'ALARM'
    print('   a session reading %3d records: %s' % (x, verdict))

# ---- 2. the operational model: a fixed limit, no history needed -----------------------------
print()
print('2. OPERATIONAL MODEL: more than 10 password failures in a minute')
for failures in (2, 11, 37):
    print('   %2d failures: %s' % (failures, 'ALARM' if failures > 10 else 'normal'))

# ---- 3. signatures: patterns of known attacks (SP 800-94, section 2.3.1) ---------------------
SIGNATURES = {
    'telnet login as root': lambda e: e['service'] == 'telnet' and e['user'] == 'root',
    'known malware attachment': lambda e: e.get('attachment') == 'freepics.exe',
    'auditing disabled (status 645)': lambda e: e.get('status') == 645,
}
events = [
    {'service': 'ssh', 'user': 'chetan'},
    {'service': 'telnet', 'user': 'root'},
    {'service': 'mail', 'user': 'asha', 'attachment': 'freepics.exe'},
    {'service': 'mail', 'user': 'asha', 'attachment': 'freepics2.exe'},
    {'service': 'log', 'user': 'system', 'status': 645},
]
print()
print('3. SIGNATURE-BASED DETECTION')
for e in events:
    hits = [name for name, rule in SIGNATURES.items() if rule(e)]
    what = (e.get('attachment') or ('status %d' % e['status'] if 'status' in e else '')
            or e['service'] + ' as ' + e['user'])
    print('   %-16s %s' % (what, ', '.join(hits) if hits else 'no signature matches'))

# ---- 4. the trade-off: move the threshold, and both rates move --------------------------------
honest = [max(0, round(rng.gauss(40, 8))) for _ in range(10_000)]
attacks = [max(0, round(rng.gauss(75, 25))) for _ in range(500)]
print()
print('4. ONE DETECTOR, FIVE THRESHOLDS (10,000 honest sessions, 500 intrusions)')
print('   d     alarm above   intrusions caught   honest sessions flagged')
for d in (1, 1.5, 2, 3, 4):
    limit = mean + d * stdev
    caught = sum(x > limit for x in attacks) / len(attacks)
    flagged = sum(x > limit for x in honest) / len(honest)
    print('   %-5s %11.1f %18.1f%% %24.2f%%' % (d, limit, 100 * caught, 100 * flagged))
munotes.in572

Intrusion Detection: Statistical Anomaly and Rule-Based

1. MEAN AND STANDARD DEVIATION MODEL (60 past sessions)
   mean 40.3 records, standard deviation 8.3
   normal range at d = 4: 7.0 to 73.5 records
   Chebyshev: at most 6.25% of honest sessions can fall outside it
   a session reading  44 records: normal
   a session reading  71 records: normal
   a session reading 400 records: ALARM

2. OPERATIONAL MODEL: more than 10 password failures in a minute
    2 failures: normal
   11 failures: ALARM
   37 failures: ALARM

3. SIGNATURE-BASED DETECTION
   ssh as chetan    no signature matches
   telnet as root   telnet login as root
   freepics.exe     known malware attachment
   freepics2.exe    no signature matches
   status 645       auditing disabled (status 645)

4. ONE DETECTOR, FIVE THRESHOLDS (10,000 honest sessions, 500 intrusions)
   d     alarm above   intrusions caught   honest sessions flagged
   1            48.6               87.8%                    14.67%
   1.5          52.7               84.6%                     5.98%
   2            56.9               81.2%                     2.13%
   3            65.2               69.2%                     0.10%
   4            73.5               55.6%                     0.00%
munotes.in573

Intrusion Detection: Statistical Anomaly and Rule-Based

What the run establishes, in order.

The profile catches the large departure and passes the small one. At d = 4 the clerk's normal range is 7 to 73.5 records a session. A session reading 400 records, a misfeasor copying the database, raises an alarm; one reading 71 does not, although it is well above average. Chebyshev guarantees that at most 6.25 per cent of honest sessions can fall outside the range, whatever the shape of the data.

A fixed limit needs no history at all. Eleven password failures in a minute raise an alarm for anyone. That is the operational model's appeal and its weakness.

A signature catches exactly what it describes, and nothing else. Telnet as root, the known attachment and the log code for auditing switched off each match. The attachment renamed "freepics2.exe" matches nothing, which is SP 800-94's own illustration of how easily a signature is evaded.

Every threshold is a trade. At d = 1 the detector catches 87.8 per cent of intrusions but flags 14.67 per cent of honest sessions; at d = 4 it flags none of 10,000 honest sessions but catches only 55.6 per cent of intrusions. Plotting the share of intrusions caught against the share of honest activity flagged, for every possible threshold, gives what engineers call the receiver operating characteristic, or ROC curve. There is no setting that is right for everyone: an operator chooses a point on the curve, and the base-rate arithmetic of the intruders chapter shows why that point must lie very near zero false alarms.

Four kinds of IDS, by what they watch

SP 800-94 sorts intrusion detection and prevention systems by "the types of events that they monitor and the ways in which they are deployed":

KindWatchesSees
Network-basedthe traffic on a network segmentattacks carried in network and application protocols
Wirelessthe radio traffic of wireless networksattacks on the wireless protocols themselves, rogue access points
Network behaviour analysisthe pattern of flows across a networkunusual traffic flows, such as denial of service, some malware, policy violations
Host-basedone computer and the events on itwhat happens inside that host: logs, files, processes
munotes.in574

Intrusion Detection: Statistical Anomaly and Rule-Based

"For most environments, a combination of network-based and host-based IDPS technologies is needed", SP 800-94 advises. The next chapter follows the host-based side into its audit records and the network side into distributed IDS and honeypots.

Distinctions that carry marks

Statistical anomaly detectionRule-based penetration identification
Compares behaviour withwhat is normal for this user or systempatterns of known attacks and misuse
Needsa period of learningexperts to write rules
Catches unknown attacksyesno
Main weaknessfalse alarms; profiles poisoned or slowly driftedmisses new attacks and simple variants
Threshold detectionProfile-based detection
Limits setonce, for everyonefrom each user's own history
Denning's modeloperationalmean and standard deviation, multivariate, Markov, time series

What beginners get wrong here

Confusing rule-based anomaly detection with penetration identification. Both use rules, but the first describes normal use and flags departures; the second describes attacks and flags matches.

Saying anomaly detection finds only known attacks. It is the signature approach that needs to know the attack; anomaly detection's advantage is that it does not.

Believing a stricter threshold is simply better. Lowering the threshold catches more intrusions and raises more false alarms; the two always move together.

Treating an IDS as protection. An intrusion detection system reports; only an intrusion prevention system tries to stop an attack, and even it cannot stop what it does not detect.

Quick revision

  • Intrusion detection: monitoring events and analysing them "for signs of possible incidents" (SP 800-94). Why: stop intrusions early, deter, learn.
  • Statistical anomaly: threshold detection (fixed limits, all users) and profile-based (per user).
  • Denning, 1987: metrics event counter, interval timer, resource measure (textbooks add gauge); models operational, mean and standard deviation, multivariate, Markov process, time series. Chebyshev: at most 1/d^2 outside d standard deviations.
  • Rule-based: anomaly detection (rules of past behaviour) and penetration identification (rules of known attacks, the signatures).
  • Signatures miss variants: "freepics2.exe".
  • ROC: intrusions caught against honest activity flagged, for every threshold.
  • SP 800-94's four kinds: network-based, wireless, network behaviour analysis, host-based.

Test yourself

1. Distinguish statistical anomaly detection from rule-based detection. Statistical anomaly detection collects data on the behaviour of legitimate users over time and applies statistical tests to decide whether new behaviour is normal; it can detect attacks never seen before but raises false alarms and can be fooled by gradual change. Rule-based detection applies a set of rules: either rules derived from past usage, flagging departures, or rules describing known attacks and suspicious behaviour, flagging matches. The second form catches known attacks reliably but misses new ones and simple variants.

munotes.in575

Intrusion Detection: Statistical Anomaly and Rule-Based

2. Name and explain the metrics used in profile-based intrusion detection. Denning defines three. An event counter is the number of audit records of some kind in a period, such as logins in an hour. An interval timer is the time between two related events, such as successive logins. A resource measure is the quantity of a resource used by an action, such as pages printed in a day or CPU time used by a program. Textbooks add a gauge, a value that can rise and fall, such as the number of open connections.

3. Explain the mean and standard deviation model with its guarantee. From past observations of a metric the system computes the mean and standard deviation, and defines a new observation as abnormal if it lies more than d standard deviations from the mean. By Chebyshev's inequality, whatever the distribution of the data, at most 1/d^2 of normal observations can lie outside that interval; for d = 4 that is at most 6.25 per cent. The model needs no prior knowledge of normal activity, learns from each user's own data, and so can treat as normal for one user what is abnormal for another.

4. What is rule-based penetration identification, and what is its weakness? It is an expert-system approach in which rules describe known attacks, known weaknesses and suspicious behaviour, and activity matching a rule is reported as a likely intrusion; these rules are what NIST calls signatures. Its weakness is that it can only find what its rules describe: a new attack, or a small variant of a known one such as a renamed file, matches no rule and passes unnoticed.

5. What does moving the threshold of an anomaly detector do? A lower threshold flags more activity, catching more intrusions but also flagging more honest activity as false alarms; a higher threshold does the reverse. Each threshold gives one pair of rates, and the curve through all of them is the receiver operating characteristic. Because intrusions are rare compared with honest activity, the threshold must usually be set to keep false alarms very low, at the cost of missing some intrusions.

Contents This chapter on its own page

munotes.in576

Chapter Eighty-Six

Audit Records, Distributed Intrusion Detection and Honeypots

Syllabus topic Module 2, "Intrusion Detection"

In one line

Intrusion detection runs on audit records, which a system can produce for its own purposes or specially for the detector; a distributed IDS gathers them from many hosts and the network into one place, and a honeypot is a decoy that no honest user has any reason to touch, so anything that touches it is worth an alarm.

In the words an answer should use: a fundamental tool for intrusion detection is the audit record, a record of ongoing activity by users. There are two kinds. Native audit records are those that multi-user operating systems already produce through their accounting software; they need no additional software but may not contain the information needed, or not in a convenient form. Detection-specific audit records are generated by a collection facility solely for the intrusion detection system; they can be made vendor-independent and portable, at the cost of extra overhead. A distributed intrusion detection system coordinates detection across many hosts and the network, and a honeypot is a decoy system designed to lure an attacker away from critical systems.

Audit records

Native records are what the system already keeps: login records, authentication logs, the Windows event log, a web server's access log. Using them costs nothing extra. But they were designed for accounting or debugging, so a field the detector needs may be missing, and every product writes its own format.

Detection-specific records are produced by a collection facility written for the detector, in one fixed format. They carry exactly what detection needs and look the same on every system, but the system now keeps two sets of records, which costs processing and storage.

Denning's format. Dorothy Denning's 1987 model defined a detection-specific audit record as a six-part tuple:

FieldHolds
Subjectwho acted: a user, or a process acting for one
Actionwhat was done: login, logout, read, execute, write
Objectwhat it was done to: a file, a program, a device
Exception-Conditionthe error the system actually raised, if any
Resource-Usagehow much was used: records read, lines printed, CPU time
Time-stampwhen

Every action is broken into single-object actions, so that each record names exactly one object. Copying a file is three records: executing the copy program, reading the source, writing the destination. Denning gives three reasons: objects are what a system protects, so the records line up with the access controls; one object per record keeps the model simple; and most real systems already record one object at a time.

The two kinds meet in a filter. Denning's remedy for the untidiness of native records was to write "a filter that translates the records into a standard format". Most detectors today do exactly that: they read native logs and turn them into records of their own.

munotes.in577

Audit Records, Distributed Intrusion Detection and Honeypots

In India the logs must be kept. CERT-In's directions of 28 April 2022 require service providers, data centres, companies and government organisations to keep the logs of all their systems "for a rolling period of 180 days" within India, and to synchronise their clocks with the national time servers, so that records from different machines can be put in the right order.

Distributed intrusion detection

A college or company has many computers, and an attacker who moves between them leaves a little evidence on each. A detector on one host sees only its own part; the pattern appears only when the parts are put together. A distributed IDS therefore has to cope with different audit formats on different systems, decide where the analysis happens, and protect the audit data while it travels to that place.

DIDS (1991, 1992), the prototype from the University of California at Davis and its partners, set the architecture most textbooks describe:

DIDS componentTextbook nameDoes
Host Monitor, one per hosthost agent modulecollects the host's audit records, filters and analyses them locally, sends notable events on
LAN Monitor, one per LAN segmentLAN monitor agent modulewatches the segment's traffic: connections between hosts, services used, volume
Directorcentral manager modulereceives both, correlates them in an expert system, and decides

Its paper describes "distributed monitoring and data reduction" with "centralized data analysis". The problem it was built to solve it called Network-user Identification: following one person across the network "possibly with a new user-id on each computer". Only the Director, which sees both the logins and the connections between hosts, can tell that three accounts on three machines are one person.

Honeypots

A honeypot is, in the definition Lance Spitzner quotes, "an information system resource whose value lies in unauthorized or illicit use of that resource". It is a system, a file, an account or even a fake record that no legitimate user has any reason to touch.

What it is for. A honeypot can draw an attacker away from the systems that matter, record what the attacker does, and hold the attacker's attention long enough for the defenders to respond.

Why its alarms are worth reading. A honeypot has "no production value", so "Anything or anyone interacting with the honeypot is an anomaly". Spitzner's point is that honeypots "dramatically reduce false positives": the base-rate problem of the intruders chapter hardly arises, because honest activity on a honeypot is close to zero. A fake password or database record planted for the same purpose is called a honeytoken; a whole network of real decoy systems is a honeynet.

munotes.in578

Audit Records, Distributed Intrusion Detection and Honeypots

Its limits and risks. A honeypot sees only what touches it, what Spitzner calls a "Limited Field of View". And because it invites attack, "there is a risk that an attacker could use a honeypot to attack or harm other non-honeypot systems"; the more realistic the honeypot, the greater the risk.

The legal side. Section 43 of the IT Act turns on acting "without permission of the owner" of a computer network, so a honeypot belongs only on a network whose owner has agreed to it, in writing; a student should never set one up on the college network on their own. Whatever a honeypot records, other people's addresses and messages among it, has to be kept securely and used only for the security purpose it was collected for.

Reading a Snort rule

Snort is an open-source network intrusion detection and prevention system, and the practical asks for a rule of your own. A Snort rule is a header and, in brackets, options. Snort's own guide lists the header's five parts:

alert tcp $EXTERNAL_NET any -> $HOME_NET 23 (msg:"TELNET login attempt as root"; content:"root",nocase; sid:1000001; rev:1;)
PartIn the ruleMeans
Actionalertwhat to do when the rule fires
Protocoltcpwhich protocol it applies to
Addresses$EXTERNAL_NET, $HOME_NETsource and destination networks, set in Snort's configuration
Portsany, 23source and destination ports; 23 is telnet
Direction->from the first address and port to the second
msg"TELNET login attempt as root"the text printed with the alert
content"root",nocasethe bytes to look for; modifiers such as nocase are "written alongside the content string, separated by commas"
sid, rev1000001, 1the rule's number and version; Snort reserves sids below 1,000,000, so local rules "start at 1000000"

The run: records, a Director, and two rules

The listing prints Denning's own three records, filters some authentication-log lines written for the example into Denning records and counts the failures, lets a DIDS-style Director follow one person across three hosts, and then parses and applies two Snort rules, one of them a honeypot rule.

# Audit records in Denning's form; a distributed IDS following one person across hosts (DIDS);
# and Snort 3 rules read field by field and run against packets.
import ipaddress, re
from collections import Counter

# ---- 1. detection-specific audit records: Denning's own example (1987) ------------------------
FIELDS = ('subject', 'action', 'object', 'exception', 'resource', 'time')
COPY = [('Smith', 'execute', '<Library>COPY.EXE', '0', 'CPU=00002', 11058521678),
        ('Smith', 'read', '<Smith>GAME.EXE', '0', 'RECORDS=0', 11058521679),
        ('Smith', 'write', '<Library>GAME.EXE', 'write-viol', 'RECORDS=0', 11058521680)]
print('1. "COPY GAME.EXE TO <Library>GAME.EXE" BECOMES %d SINGLE-OBJECT RECORDS' % len(COPY))
print('   <%s>' % ', '.join(f.capitalize() for f in FIELDS))
for record in COPY:
    print('   (%s)' % ', '.join(str(x) for x in record))
violations = Counter(r[0] for r in COPY if r[3] != '0')
print('   exception conditions for Smith: %d' % violations['Smith'])

# ---- 2. native records need a filter before they can be used ---------------------------------
NATIVE = ['10:14:0%d lab-pc sshd: Failed password for admin from 10.0.4.66' % s for s in range(9)]
NATIVE += ['10:14:%d lab-pc sshd: Failed password for admin from 10.0.4.66' % s
           for s in range(10, 13)]
NATIVE += ['10:15:30 lab-pc sshd: Failed password for chetan from 10.0.4.17',
           '10:15:41 lab-pc sshd: Accepted password for chetan from 10.0.4.17']
LINE = re.compile(r'(\d\d:\d\d):\d\d (\S+) sshd: (Failed|Accepted) password for (\S+) from (\S+)')
records = []
for line in NATIVE:
    minute, host, outcome, user, source = LINE.match(line).groups()
    records.append((user, 'login', host + '/sshd', '0' if outcome == 'Accepted' else 'auth-fail',
                    'from ' + source, minute))
failures = Counter((r[0], r[5]) for r in records if r[3] == 'auth-fail')
print()
print('2. %d NATIVE LOG LINES, FILTERED INTO DENNING RECORDS, THEN COUNTED' % len(NATIVE))
for (user, minute), count in sorted(failures.items()):
    print('   %-6s %s  failed logins %2d: %s' % (user, minute, count,
                                                'ALARM' if count > 10 else 'normal'))

# ---- 3. DIDS: host monitors report logins, the LAN monitor reports connections --------------
lan = {('pc-12', 50311): 'server-a', ('server-a', 40022): 'db-1'}    # (from host, port): to host
logins = [('pc-12', 'asha', None),                                   # at the console
          ('server-a', 'webadmin', ('pc-12', 50311)),
          ('db-1', 'root', ('server-a', 40022))]
nid, next_nid, where = {}, 1, {}
for host, user, via in logins:
    if via is None:                              # a new person enters the network
        nid[(host, user)], next_nid = next_nid, next_nid + 1
    else:                                        # the Director joins the two monitors' reports
        came_from = [key for key in where if key[0] == via[0]][0]
        assert lan[via] == host
        nid[(host, user)] = nid[came_from]
    where[(host, user)] = nid[(host, user)]
print()
print('3. THE DIRECTOR\'S NETWORK-USER IDENTIFICATION')
for (host, user), n in nid.items():
    print('   %-8s on %-9s is network user %d' % (user, host, n))

# ---- 4. Snort 3 rules, parsed and applied -----------------------------------------------------
RULES = ['alert tcp $EXTERNAL_NET any -> $HOME_NET 23 '
         '(msg:"TELNET login attempt as root"; content:"root",nocase; sid:1000001; rev:1;)',
         'alert ip any any -> 10.0.9.99 any (msg:"HONEYPOT touched"; sid:1000002; rev:1;)']
HOME = ipaddress.ip_network('10.0.0.0/16')

def addr_ok(spec, ip):
    ip = ipaddress.ip_address(ip)
    if spec == 'any':
        return True
    if spec in ('$HOME_NET', '$EXTERNAL_NET'):
        return (ip in HOME) == (spec == '$HOME_NET')
    return ip == ipaddress.ip_address(spec)

def parse(rule):
    head, body = rule.split(' (', 1)
    action, proto, src, sport, arrow, dst, dport = head.split()
    options = dict(re.findall(r'(\w+):((?:"[^"]*"|[^;])*);', body))
    return dict(action=action, proto=proto, src=src, sport=sport, arrow=arrow, dst=dst,
                dport=dport, **options)

def fires(r, pkt):
    if r['proto'] not in ('ip', pkt['proto']):
        return False
    if not (addr_ok(r['src'], pkt['src']) and addr_ok(r['dst'], pkt['dst'])):
        return False
    if r['dport'] != 'any' and int(r['dport']) != pkt['dport']:
        return False
    if 'content' in r:
        text, *mods = r['content'].split(',')
        text, payload = text.strip('"'), pkt['payload']
        if 'nocase' in mods:
            text, payload = text.lower(), payload.lower()
        return text in payload
    return True

print()
print('4. A SNORT 3 RULE, FIELD BY FIELD')
r = parse(RULES[0])
for key in ('action', 'proto', 'src', 'sport', 'arrow', 'dst', 'dport', 'msg', 'content', 'sid'):
    print('   %-8s %s' % (key, r[key]))
packets = [dict(proto='tcp', src='203.0.113.9', dst='10.0.1.5', dport=23, payload='login: ROOT'),
           dict(proto='tcp', src='203.0.113.9', dst='10.0.1.5', dport=22, payload='login: root'),
           dict(proto='tcp', src='10.0.2.7', dst='10.0.1.5', dport=23, payload='login: root'),
           dict(proto='tcp', src='203.0.113.9', dst='10.0.1.5', dport=23, payload='login: chetan'),
           dict(proto='udp', src='10.0.3.40', dst='10.0.9.99', dport=161, payload='')]
print('   packets:')
for pkt in packets:
    hits = [parse(rule) for rule in RULES if fires(parse(rule), pkt)]
    print('   %-12s -> %-9s port %-4d %-15r %s'
          % (pkt['src'], pkt['dst'], pkt['dport'], pkt['payload'],
             ', '.join('%s (sid %s)' % (h['msg'].strip('"'), h['sid']) for h in hits) or '-'))
munotes.in579

Audit Records, Distributed Intrusion Detection and Honeypots

1. "COPY GAME.EXE TO <Library>GAME.EXE" BECOMES 3 SINGLE-OBJECT RECORDS
   <Subject, Action, Object, Exception, Resource, Time>
   (Smith, execute, <Library>COPY.EXE, 0, CPU=00002, 11058521678)
   (Smith, read, <Smith>GAME.EXE, 0, RECORDS=0, 11058521679)
   (Smith, write, <Library>GAME.EXE, write-viol, RECORDS=0, 11058521680)
   exception conditions for Smith: 1

2. 14 NATIVE LOG LINES, FILTERED INTO DENNING RECORDS, THEN COUNTED
   admin  10:14  failed logins 12: ALARM
   chetan 10:15  failed logins  1: normal

3. THE DIRECTOR'S NETWORK-USER IDENTIFICATION
   asha     on pc-12     is network user 1
   webadmin on server-a  is network user 1
   root     on db-1      is network user 1

4. A SNORT 3 RULE, FIELD BY FIELD
   action   alert
   proto    tcp
   src      $EXTERNAL_NET
   sport    any
   arrow    ->
   dst      $HOME_NET
   dport    23
   msg      "TELNET login attempt as root"
   content  "root",nocase
   sid      1000001
   packets:
   203.0.113.9  -> 10.0.1.5  port 23   'login: ROOT'   TELNET login attempt as root (sid 1000001)
   203.0.113.9  -> 10.0.1.5  port 22   'login: root'   -
   10.0.2.7     -> 10.0.1.5  port 23   'login: root'   -
   203.0.113.9  -> 10.0.1.5  port 23   'login: chetan' -
   10.0.3.40    -> 10.0.9.99 port 161  ''              HONEYPOT touched (sid 1000002)
munotes.in580

Audit Records, Distributed Intrusion Detection and Honeypots

What the run establishes, in order.

One command, three records. Denning's example turns into three single-object records, and the third carries an exception condition, write-viol: Smith tried to write where he may not. Counting exception conditions is how such a detector notices attempts on the access controls.

Native records become useful after a filter. Fourteen log lines become fourteen Denning records; counting failed logins per user per minute gives 12 for "admin" in one minute, above the limit of 10, and one for chetan, a mistyped password.

The Director sees one person where the hosts see three. asha on pc-12, webadmin on server-a and root on db-1 are each a different account to their own host. Joining each login to the connection the LAN Monitor saw arrive gives all three the same network user number.

The rule fires on exactly what it describes. The Telnet rule fires on "ROOT" from outside, thanks to nocase, and not on the same text to port 22, nor from inside the network, nor on another user name. The honeypot rule fires on any packet at all to the decoy address, because nothing honest goes there.

munotes.in581

Audit Records, Distributed Intrusion Detection and Honeypots

Distinctions that carry marks

Native audit recordsDetection-specific audit records
Produced bythe operating system's own accountinga facility built for the IDS
Extra costnonea second set of records
Contentswhat accounting neededwhat detection needs
Formatone per productone, portable
Host-based componentNetwork-based component
DIDS nameHost MonitorLAN Monitor
Seeslogins, files, processes on one hostconnections and traffic on a segment
Misseswhat happens on other hostswhat happens inside a host
Ordinary IDS alarmHoneypot alarm
Comes fromactivity that looks unusualany activity at all on the decoy
False alarmsthe base-rate problemclose to none
Seeseverything it monitorsonly what touches the decoy

What beginners get wrong here

Thinking a honeypot protects the real systems by itself. It sees only what touches it; the real systems still need their firewalls and detectors.

Leaving a honeypot unwatched. A compromised honeypot can be used to attack others, so it must be isolated and monitored more closely than anything else.

Assuming native records have everything a detector needs. They were written for other purposes, which is why detectors filter them and why detection-specific records exist.

Writing Snort 2 syntax for Snort 3. In Snort 3 the modifiers of a content match go inside the same option, content:"root",nocase; not as a separate nocase option.

Quick revision

  • Native audit records: from the OS's accounting; free, but maybe incomplete or awkward. Detection-specific: made for the IDS; complete and portable, but extra overhead.
  • Denning's record: Subject, Action, Object, Exception-Condition, Resource-Usage, Time-stamp; each action split into single-object records (COPY is three).
  • India: CERT-In, logs kept 180 days, clocks synchronised.
  • Distributed IDS (DIDS): Host Monitor per host, LAN Monitor per segment, a central Director; solves Network-user Identification.
  • Honeypot: "an information system resource whose value lies in unauthorized or illicit use"; diverts, records, delays; almost no false alarms; risk of use against others; limited view. Honeytoken, honeynet.
  • Snort rule: header (action, protocol, addresses, ports, direction) and options (msg, content with modifiers, sid from 1,000,000, rev).

Test yourself

1. Distinguish native audit records from detection-specific audit records. Native audit records are produced by the accounting software that multi-user operating systems already have; they cost nothing extra to collect, but they may not contain the information the detector needs, or may not hold it in a convenient form. Detection-specific audit records are generated by a collection facility solely for the intrusion detection system; they contain exactly what is needed and can be vendor-independent and portable across systems, but they add overhead because the system now keeps two sets of records.

munotes.in582

Audit Records, Distributed Intrusion Detection and Honeypots

2. Describe the fields of a detection-specific audit record, with an example. Denning's record has six fields: Subject, the initiator of the action; Action, the operation performed; Object, the receptor of the action; Exception-Condition, any error the system raised; Resource-Usage, the quantity of resources used; and Time-stamp, when the action took place. Each action is decomposed into single-object actions, so the command COPY GAME.EXE TO <Library>GAME.EXE issued by Smith produces three records: execute of the COPY program, read of Smith's GAME.EXE, and write of the Library copy, the last with the exception condition write-viol because Smith lacked write permission.

3. Describe the architecture of a distributed intrusion detection system. In DIDS, the model most textbooks follow, a Host Monitor on each host collects and analyses that host's audit records and sends notable events on; a LAN Monitor on each network segment watches traffic between hosts, such as connections and services used; and a central Director, containing an expert system, receives reports from both, correlates them and decides whether an intrusion is under way. Because it sees both logins and connections, the Director can follow one person moving between hosts under different user names, the network-user identification problem.

4. What is a honeypot? State its purposes and risks. A honeypot is a decoy resource whose value lies in being used by attackers: a system, file or account that no legitimate user has reason to touch. It diverts attackers from critical systems, collects information about what they do, and keeps them engaged long enough for administrators to respond; because any activity on it is suspicious, its alarms are almost free of false positives. Its risks are that a compromised honeypot may be used to attack other systems, and that it sees only the activity directed at it; it must be set up only with the network owner's permission and watched closely.

5. Explain the parts of the Snort rule alert tcp $EXTERNAL_NET any -> $HOME_NET 23 (msg:"TELNET login attempt as root"; content:"root",nocase; sid:1000001; rev:1;). The header says: raise an alert, for TCP traffic, from any port on an address outside the protected network, going to port 23, telnet, on an address inside it. The options say: print the message "TELNET login attempt as root"; fire only if the packet's data contains "root" in any mixture of upper and lower case; and identify the rule as number 1000001, revision 1, a local rule number since Snort reserves numbers below 1,000,000.

Contents This chapter on its own page

munotes.in583

Chapter Eighty-Seven

Malicious Software: The Taxonomy

Syllabus topic Module 2, "Malicious Software: Viruses and Related Threats"

In one line

Malicious software is sorted by two questions: does it need a host program to live in, and does it make copies of itself? A virus needs a host and copies itself; a worm copies itself on its own; trapdoors, logic bombs and Trojan horses hide in or behind programs and do not copy themselves; a zombie stands alone and waits for orders.

In the words an answer should use: malicious software, or malware, is, in NIST's words, "a program that is covertly inserted into another program with the intent to destroy data, run destructive or intrusive programs, or otherwise compromise the confidentiality, integrity, or availability of the victim's data, applications, or operating system". Malicious programs divide into those that need a host program, fragments that cannot exist independently of some application, utility or system program, and those that are independent, self-contained programs the operating system can schedule and run. They also divide into those that do not replicate and those that replicate, producing copies of themselves to be activated later.

The map

A two by two grid. Columns: needs a host program, stands alone. Rows: makes copies of itself, makes no copies. Needs a host and makes copies: virus, with file infector, boot sector, macro and scripting kinds. Stands alone and makes copies: worm, with network service and mass mailing kinds. Needs a host and makes no copies: trapdoor or backdoor, logic bomb, Trojan horse, starred. Stands alone and makes no copies: zombie or bot, and the attacker tools malware installs, such as keyloggers and rootkits. Below: any of them can carry a payload, such as ransomware, data theft or a bot. The star notes that NIST calls a Trojan horse self-contained, a whole program.

Figure 87.1 Two questions sort the family: does it need a host program, and does it make copies of itself?

KindNeeds a hostReplicatesIn one sentence
Trapdoor (backdoor)yesnoa secret way into a program or system that skips the normal checks
Logic bombyesnocode that does harm when a condition becomes true
Trojan horseyes, in the textbook; NIST says it is self-containednoa program that looks useful and has a hidden harmful purpose
Virusyesyesinserts copies of itself into other programs or files
Wormnoyesa program that copies itself to other machines, usually without anyone's help
Zombie (bot)nonoa program that lets someone else command the machine, often to attack others

The kinds, one by one

Trapdoor, or backdoor. NIST defines a backdoor as "An undocumented way of gaining access to computer system". Programmers have long left such entry points to test or debug a program without going through its full login; left in, they become a way in for anyone who knows them. Ken Thompson, in his 1984 Turing Award lecture, described the most unsettling one. He altered the C compiler so that, whenever it compiled the Unix login program, it added code accepting "either the intended encrypted password or a particular known password", and then taught the compiler to reinsert that change whenever it compiled itself. After that, "the login command will remain bugged with no trace in source anywhere". His conclusion: "The moral is obvious. You can't trust code that you did not totally create yourself."

Logic bomb. In the definition NIST's glossary takes from CNSSI 4009, "A piece of code intentionally inserted into a software system that will set off a malicious function when specified conditions are met." The condition may be a date, the presence or absence of a file, or a particular user's name disappearing from the payroll. Until the condition is met, the program works normally, which is why logic bombs are usually planted by insiders who can put code into the software.

munotes.in584

Malicious Software: The Taxonomy

Trojan horse. SP 800-83 defines it as "a self-contained, nonreplicating program that, while appearing to be benign, actually has a hidden malicious purpose". A free game, a "crack" for paid software, or an app that promises to show who viewed your profile may do what it claims and also steal saved passwords or open a backdoor. Textbooks draw it with the kinds that need a host, because the harmful code hides inside something useful; NIST calls it self-contained, because it is a whole program of its own, not a parasite on another file. Both are describing the same thing from different sides.

Virus. "A virus self-replicates by inserting copies of itself into host programs or data files", SP 800-83 says, and is "often triggered through user interaction, such as opening a file or running a program". It comes as compiled viruses, run by the operating system (file infectors, boot sector viruses, and multipartite ones that do both), and interpreted viruses, run by an application (macro viruses in documents, scripting viruses). The next chapter takes a virus apart.

Worm. "A worm is a self-replicating, self-contained program that usually executes itself without user intervention." Network service worms exploit a flaw in a network service to spread; mass mailing worms spread by email like a virus but as programs of their own. The chapter after next is about worms.

Zombie, or bot. SP 800-83 lists zombies, "better known as bots", among backdoors: programs "installed on a host to cause it to attack other hosts". A network of them under one controller is a botnet, the engine of the distributed denial of service chapters.

Attacker tools. Malware often installs tools that are not malware in the strict sense because they cannot spread themselves: keystroke loggers, which record what is typed, and rootkits, collections of files that change a system "in a malicious and stealthy way" and hide their own presence.

Ransomware. NIST's ransomware profile, IR 8374 (2022), defines it as "a type of malware that encrypts an organization's data and demands payment as a condition of restoring access to that data", and warns that it "can also be used to steal an organization's information and demand additional payment in return for not disclosing the information". Ransomware is a payload, not a new branch of the map: it arrives as a Trojan in an attachment, by a worm through an unpatched service, or through a stolen password.

munotes.in585

Malicious Software: The Taxonomy

Why the map is no longer enough

SP 800-83 is blunt: "Many, if not most, instances of malware today are blended attacks", combining several ways of spreading, and "the classic malware categories listed above (virus, worm, etc.) are considerably less useful than they used to be for malware incident handling". Malware that arrives by a phishing email as a Trojan, spreads across the network like a worm, installs a rootkit and a keylogger, and finally encrypts everything for ransom, fits every box at once. The map remains useful for the exam and for understanding; for handling a real incident, what matters is what the program does.

The run: the whole of replication in two lines

What separates viruses and worms from everything else is that they copy themselves. Thompson began his lecture with the programming puzzle behind that: "write a source program that, when compiled and executed, will produce as output an exact copy of its source". The two-line program below does exactly that and nothing else; the listing runs it and compares.

s = 's = %r\nprint(s %% s)'
print(s % s)
# Thompson's Stage I: run quine.py and compare what it prints with its own text.
import contextlib, io

source = open('quine.py').read()
printed = io.StringIO()
with contextlib.redirect_stdout(printed):
    exec(source, {})
output = printed.getvalue()
print(output, end='')
print('--- the output is the source, character for character:', output == source)

again = io.StringIO()
with contextlib.redirect_stdout(again):
    exec(output, {})
print('--- and the copy, run in its turn, prints itself too:', again.getvalue() == source)
print('--- the program is %d characters in %d lines, and reads no file to do it'
      % (len(source), source.count('\n')))
s = 's = %r\nprint(s %% s)'
print(s % s)
--- the output is the source, character for character: True
--- and the copy, run in its turn, prints itself too: True
--- the program is 41 characters in 2 lines, and reads no file to do it

What the run establishes, in order.

A program can reproduce itself exactly. Its output is its own source, character for character, and the copy, run again, reproduces itself again. The program reads no file to do it: the copy is built from a string inside the program.

Replication is the easy part. A virus is such a program that writes its copy into another program; a worm is one that sends its copy to another machine and runs it there. The difficult and dangerous parts are finding where to put the copy and what the copy does once it is there, which is what the next two chapters are about.

munotes.in586

Malicious Software: The Taxonomy

Distinctions that carry marks

VirusWormTrojan horse
Needs a hostyes: a program or filenono, by NIST; its harm hides in a useful-looking program
Replicatesyes, into filesyes, across the networkno
Needs a person to actusually, to open or runusually notyes, to install and run it
Typical defenceantivirus, not opening unknown filespatching network services, firewallsinstalling only trusted software
TrapdoorLogic bomb
Isa hidden way ina hidden trigger for harm
Used bywhoever knows it existsthe program itself, when the condition is met
Typical authora developer, often for testingan insider

What beginners get wrong here

Calling every malicious program a virus. A virus is the kind that inserts itself into other files; worms, Trojan horses, bots and ransomware are different things.

Saying a Trojan horse spreads itself. It does not replicate; people install it, believing it to be something else.

Treating ransomware as a separate category like a worm. Ransomware describes what the program does, encrypt and demand payment; it arrives as a Trojan, a worm or through a stolen password.

Thinking reading the source code settles the question. Thompson's compiler put a trapdoor into the login program with no trace in any source file.

Quick revision

  • Malware: a program "covertly inserted into another program" to harm confidentiality, integrity or availability (SP 800-83).
  • Two questions: needs a host program? and replicates?
  • Host, no copies: trapdoor (backdoor), logic bomb, Trojan horse (NIST: "self-contained"). Host, copies: virus. Alone, copies: worm. Alone, no copies: zombie (bot).
  • Virus kinds: compiled (file infector, boot sector, multipartite) and interpreted (macro, scripting). Worm kinds: network service, mass mailing.
  • Attacker tools: keystroke loggers, rootkits. Ransomware: a payload that encrypts and extorts, often twice.
  • Most malware today is a blended attack; the categories matter less for handling than they did.
  • Thompson, 1984: a compiler trapdoor "with no trace in source anywhere"; a program can print its own source.

Test yourself

1. Classify malicious programs by whether they need a host and whether they replicate. Programs that need a host program and do not replicate are trapdoors, logic bombs and, in the textbook's classification, Trojan horses. The virus needs a host and replicates. The worm is independent and replicates. The zombie, or bot, is independent and does not replicate. NIST describes the Trojan horse as a self-contained program, because it is a whole program rather than code attached to another file.

2. Define a trapdoor and a logic bomb, with an example of each. A trapdoor, or backdoor, is a secret, undocumented entry point into a program or system that lets someone who knows it gain access without the usual security checks; a developer's hidden debugging password left in a login program is one, and Thompson's modified compiler inserted one into the Unix login program. A logic bomb is code intentionally inserted into software that performs a malicious function when specified conditions are met, such as a programmer's code that deletes the payroll records on the day his own name is removed from them.

munotes.in587

Malicious Software: The Taxonomy

3. What is a Trojan horse? How does it differ from a virus? A Trojan horse is a program that appears useful or harmless but has a hidden malicious purpose, such as stealing passwords or installing a backdoor. It does not replicate: users install it themselves, deceived by what it appears to be. A virus, in contrast, copies itself into other programs or files, so that it spreads each time an infected program is run or an infected file opened.

4. What is ransomware, and where does it fit in the classification? Ransomware is malware that encrypts an organisation's or person's data and demands payment for restoring access, and increasingly also steals the data and demands more for not publishing it. It is a kind of payload rather than a branch of the classification: it reaches its victims as a Trojan horse in an attachment or download, through a worm exploiting an unpatched service, or through stolen credentials.

5. Why does NIST say the classic malware categories are less useful than they used to be? Because most malware today is a blended attack that combines several methods: it may arrive as a Trojan horse by phishing, spread like a worm, install attacker tools such as rootkits and keyloggers, and deliver a payload such as ransomware, so it fits several categories at once. Handling an incident therefore depends on what the particular program does, and one set of procedures now serves for all malware.

Contents This chapter on its own page

munotes.in588

Chapter Eighty-Eight

Viruses: The Four Phases, the Structure and the Types

Syllabus topic Module 2, "Malicious Software: Viruses and Related Threats"

In one line

A virus is a piece of code that attaches itself to another program and copies itself into more programs when that one runs; it passes through four phases, dormant, propagation, triggering and execution, and everything clever about a virus is an attempt to make some particular check fail to notice it.

In the words an answer should use: a virus is, in Fred Cohen's original definition, "a program that can 'infect' other programs by modifying them to include a possibly evolved copy of itself". NIST puts it as a program that "self-replicates by inserting copies of itself into host programs or data files", and adds that viruses "are often triggered through user interaction, such as opening a file or running a program". During its lifetime a typical virus goes through four phases: a dormant phase, in which it is idle; a propagation phase, in which it places copies of itself into other programs or disk areas; a triggering phase, in which it is activated to perform the function it was intended for; and an execution phase, in which that function, the payload, is performed.

The four phases

PhaseWhat happensNote
Dormantthe virus is idle, waiting for an event that activates it: a date, the presence of another program, the disk filling beyond some sizenot every virus has this phase
Propagationit places a copy of itself into other programs, or into system areas of a disk; the copy may not be identical, so as to be harder to findthis is the phase that makes it a virus
Triggeringthe condition it was waiting for is met, and it prepares to perform its functionthe condition may be a date, or a count of how many times it has copied itself
Executionthe payload runs: it may be harmless, such as a message on the screen, or destructive, such as deleting filesthe harm is here, not in the copying

The same four phases describe most viruses, but a virus is defined by propagation alone. A virus whose payload is an empty procedure still consumes disk space, processing time and trust.

Where a virus attaches, and what each choice costs the defender

KindAttaches toRuns whenWhat the defender watches
File infectorexecutable programsthe program is runthe program file's length, contents and modification date
Boot sectorthe master boot record or a disk's boot sectorthe machine starts from that diskthe boot sector's contents, and the firmware's own protections
Multipartiteboth of the aboveeitherboth
Macrodocuments and templates, through the application's macro languagethe document is openedwhether macros are allowed to run at all
Scriptingscripts the operating system or a service interpretsthe script is runwhich scripts may run, and from where
munotes.in589

Viruses: The Four Phases, the Structure and the Types

NIST's own division is by what executes the virus: compiled viruses, run by the operating system, which is where file infector, boot sector and multipartite belong, and interpreted viruses, run by an application, which is where macro and scripting viruses belong.

Why macro viruses mattered so much. A macro virus infects documents, not programs. Documents are exchanged far more freely than programs, are not thought of as executable, and are the same on every platform the application runs on. The answer was not cleverer scanning but a change in the application: macros do not run unless the person allows them.

The structure, and what it is arranged to defeat

A virus that attaches to a program has to do four things when the infected program is run: check whether a chosen target is already infected, so it does not attach twice; attach a copy of itself to the target; do whatever its payload requires if the trigger condition holds; and finally hand control to the original program, so that everything appears normal.

Every one of those steps exists to keep some check from noticing.

  • The infection marker exists so that the virus does not attach twice and make the file grow twice. A marker is also the first thing a defender can look for: it is a fixed pattern that the virus itself must recognise.
  • Handing control back exists so that the user sees the program behave as expected. If the program failed or paused, someone would investigate.
  • Compression, the classic trick, exists to defeat a check on the file's length. If the virus first squeezes the host program by more than the space it needs, it can restore the file to exactly its old length. The run below does that arithmetic.

Hiding from the scanner: the kinds by concealment

KindWhat it doesWhat it defeats
Encryptedmost of the virus is stored masked under a key that changes with each copy; a small constant routine unmasks it before it runsa signature taken from the body
Stealthit interferes with the system so that reads of an infected file return the original contentsa check made through the infected system itself
Polymorphicthe body is masked differently every time and the unmasking routine is rewritten each time, so that no fixed sequence of bytes survivesa signature of any fixed part
Metamorphicthe whole virus is rewritten on each copy, so two copies may share no bytes at all and behave differently while doing the same thingsignatures altogether

Cohen proved that this arms race has no end. To decide that a program is a virus, one must decide that it infects other programs. Cohen assumed a decision procedure D that answers correctly, and then described a program that calls D and infects other programs if and only if D says it is not a virus. The procedure is then "self contradictory", so "precise determination of a virus by its appearance is undecidable". His conclusions list the undecidable problems: detecting a virus by its appearance, by its behaviour, and detecting "an evolution of a known virus". This is why no scanner can be complete, and why defence also relies on checks that do not try to recognise the attacker at all.

munotes.in590

Viruses: The Four Phases, the Structure and the Types

The run: four checks, and what each one misses

The listing walks the four phases, then tries three defences: a check on a file's length, a scan for a known sequence of bytes, and a comparison against a stored hash. Nothing in it replicates: the "infected" files are ordinary byte strings, and each step only asks whether a check would notice.

# A virus from the DEFENDER'S side: the four phases as a state machine, then three detectors
# tried against three kinds of concealment. Nothing here infects anything: the "infected" files
# are ordinary byte strings, and every step asks only whether a check would notice.
import hashlib, random, zlib

# ---- 1. the four phases, walked through in order ---------------------------------------------
PHASES = [('dormant', 'installed but idle, waiting for whatever activates it'),
          ('propagation', 'placing a copy of itself where it can'),
          ('triggering', 'the condition it was waiting for has been met'),
          ('execution', 'the payload runs: a message, or damage')]
print('1. THE FOUR PHASES')
for name, meaning in PHASES:
    print('   %-12s %s' % (name, meaning))

def phase_of(run, trigger_day):
    """Where the four phases fall as the infected program is run day after day."""
    if run == 0:
        return 'dormant'
    if run == trigger_day:
        return 'triggering'
    if run == trigger_day + 1:
        return 'execution'
    return 'propagation'

print()
print('   run of the host program   phase        copies placed so far')
copies = 0
for run in range(6):
    here = phase_of(run, 4)
    copies += here == 'propagation'
    print('   %-25s %-12s %d' % (run if run else 'not yet run', here, copies))

# ---- 2. what a size check sees ---------------------------------------------------------------
# A stand-in for a compiled program: short opcode-like groups with varying operands, so that it
# compresses about as far as real machine code does. It is a stand-in, and the run prints how far
# it actually compressed rather than assuming a figure.
rng = random.Random(1984)
GROUPS = [b'\x8b\x45\xfc', b'\x83\xc0\x01', b'\x89\x45', b'\xe8', b'\xc3',
          b'\x55\x89\xe5', b'\x83\xec', b'\x8d\x55', b'\xff\x75', b'\x50']
buf = bytearray()
while len(buf) < 12_400:
    buf += rng.choice(GROUPS) + bytes([rng.randrange(256)])
program = bytes(buf[:12_400])
ADDED = 1_800                                    # how much bigger something added would make it
plain = program + bytes(ADDED)
squeezed = zlib.compress(program, 9)
disguised = squeezed + bytes(ADDED)
print()
print('2. A SIZE CHECK, AND THE TRICK THAT WAS BUILT TO DEFEAT IT')
print('   the program as shipped            %6d bytes' % len(program))
print('   with %d bytes added              %6d bytes  size check: %s'
      % (ADDED, len(plain), 'NOTICES' if len(plain) != len(program) else 'passes'))
print('   the program compressed            %6d bytes, %.0f%% of the original'
      % (len(squeezed), 100 * len(squeezed) / len(program)))
print('   compressed, plus the same %d    %6d bytes  size check: %s'
      % (ADDED, len(disguised), 'NOTICES' if len(disguised) > len(program) else 'passes'))
print('   compressing saved %d bytes, more than the %d added, leaving %d spare'
      % (len(program) - len(squeezed), ADDED, len(program) - len(disguised)))
print('   padding those out, the file is exactly its old length again: %s'
      % (len(disguised + bytes(len(program) - len(disguised))) == len(program)))

# ---- 3. a fixed signature against three levels of concealment --------------------------------
BODY = b'\x8b\x45\xfc\x83\xc0\x01\x89\x45\xfc\xeb\xe2'      # the bytes a scanner knows
STUB = b'\x31\xc9\xb1\x0b\x80\x34\x0e'          # a fixed loop that undoes the masking

def masked(body, key):
    return bytes(b ^ key for b in body)

samples = {
    'plain': BODY,
    'masked with key 0x11': STUB + masked(BODY, 0x11),
    'masked with key 0x7d': STUB + masked(BODY, 0x7d),
    'masked, and the stub rewritten': b'\x33\xc9\xb9\x0b\x00\x30\x24\x0e' + masked(BODY, 0x42),
}
print()
print('3. ONE SIGNATURE, THREE LEVELS OF CONCEALMENT')
print('   %-32s %-14s %s' % ('sample', 'body found?', 'stub found?'))
for name, blob in samples.items():
    print('   %-32s %-14s %s' % (name, 'yes' if BODY in blob else 'no',
                                 'yes' if STUB in blob else 'no'))
print('   a scanner that looks for the body alone finds %d of %d'
      % (sum(BODY in b for b in samples.values()), len(samples)))
print('   a scanner that looks for the constant stub finds %d of %d'
      % (sum(STUB in b for b in samples.values()), len(samples)))

# ---- 4. the check that does not care how it was hidden ----------------------------------------
baseline = {'a.bin': program, 'b.bin': program, 'c.bin': program, 'd.bin': program}
stored = {name: hashlib.sha256(data).hexdigest() for name, data in baseline.items()}
padded = disguised + bytes(len(program) - len(disguised))
now = {'a.bin': program, 'b.bin': plain, 'c.bin': padded,
       'd.bin': program[:-len(BODY)] + BODY}        # same length, contents changed
print()
print('4. AN INTEGRITY CHECK AGAINST A STORED HASH')
for name in sorted(now):
    same_size = len(now[name]) == len(baseline[name])
    changed = hashlib.sha256(now[name]).hexdigest() != stored[name]
    print('   %-7s size unchanged: %-5s  hash changed: %-5s  %s'
          % (name, same_size, changed, 'ALARM' if changed else 'no change'))
print('   caught by size alone: %d of %d; caught by the hash: %d of %d'
      % (sum(len(now[n]) != len(baseline[n]) for n in now), len(now),
         sum(hashlib.sha256(now[n]).hexdigest() != stored[n] for n in now), len(now)))
munotes.in591

Viruses: The Four Phases, the Structure and the Types

1. THE FOUR PHASES
   dormant      installed but idle, waiting for whatever activates it
   propagation  placing a copy of itself where it can
   triggering   the condition it was waiting for has been met
   execution    the payload runs: a message, or damage

   run of the host program   phase        copies placed so far
   not yet run               dormant      0
   1                         propagation  1
   2                         propagation  2
   3                         propagation  3
   4                         triggering   3
   5                         execution    3

2. A SIZE CHECK, AND THE TRICK THAT WAS BUILT TO DEFEAT IT
   the program as shipped             12400 bytes
   with 1800 bytes added               14200 bytes  size check: NOTICES
   the program compressed              8493 bytes, 68% of the original
   compressed, plus the same 1800     10293 bytes  size check: passes
   compressing saved 3907 bytes, more than the 1800 added, leaving 2107 spare
   padding those out, the file is exactly its old length again: True

3. ONE SIGNATURE, THREE LEVELS OF CONCEALMENT
   sample                           body found?    stub found?
   plain                            yes            no
   masked with key 0x11             no             yes
   masked with key 0x7d             no             yes
   masked, and the stub rewritten   no             no
   a scanner that looks for the body alone finds 1 of 4
   a scanner that looks for the constant stub finds 2 of 4

4. AN INTEGRITY CHECK AGAINST A STORED HASH
   a.bin   size unchanged: True   hash changed: False  no change
   b.bin   size unchanged: False  hash changed: True   ALARM
   c.bin   size unchanged: True   hash changed: True   ALARM
   d.bin   size unchanged: True   hash changed: True   ALARM
   caught by size alone: 1 of 4; caught by the hash: 3 of 4
munotes.in592

Viruses: The Four Phases, the Structure and the Types

What the run establishes, in order.

The phases are a sequence, not a list. The virus is dormant until the program is run, spends most runs propagating, and only once reaches triggering and then execution. A defender who watches only for damage sees nothing during the runs that matter most.

A length check is worth having and easy to defeat. Adding 1,800 bytes to the program makes the file longer, and the check notices. Compressing the program first saved 3,907 bytes of the 12,400, more than the 1,800 needed, leaving enough spare to pad the file back to exactly its old length. The check then passes. The stand-in program compressed to 68 per cent of its length, about what compiled code does; the trick works whenever compression saves more than the addition costs.

A signature finds what it was given and nothing else. The scanner's eleven bytes match the unconcealed sample and none of the masked ones. Looking instead for the constant unmasking routine catches both masked versions, which is exactly what scanners do with encrypted viruses; and the variant that rewrites that routine too, the polymorphic case, is found by neither.

A stored hash does not care how the change was hidden. All three changed files fail the comparison, including the one padded back to its original length and the one whose contents changed without any change in length. This is integrity checking, and it is the answer to concealment: it recognises the file, not the attacker. Its cost is that it must be told what "correct" is, and be kept where the attacker cannot rewrite it.

munotes.in593

Viruses: The Four Phases, the Structure and the Types

Distinctions that carry marks

VirusWorm
Attaches toa host program or filenothing: it is a program
Spreads whenthe infected program is run or the file openedby itself, over the network
Usually needs a personyesno
EncryptedPolymorphicMetamorphic
Body changes each copyyes, maskedyes, maskedyes, rewritten
Unmasking routine changesnoyesthere may be none
Beaten by a signature of the bodynonono
Beaten by a signature of the routineyesnono

What beginners get wrong here

Saying the payload is what makes it a virus. Copying itself into other programs is what makes it a virus; the payload may be nothing at all.

Treating the four phases as universal. They are the textbook's description of a typical virus, and not every virus has a dormant phase or a trigger.

Thinking a longer signature list makes a scanner complete. Cohen's result is that detection by appearance is undecidable in general; a list of known patterns can only ever find what is already known.

Confusing encrypted with polymorphic. In both, the body is masked; only in a polymorphic virus is the unmasking routine itself rewritten each time, which is what defeats a signature taken from that routine.

Believing a stealth virus fools every check. It hides from reads made through the infected system; a check made from a clean system, or against a stored hash kept elsewhere, still sees the change.

Quick revision

  • Cohen, 1984: a virus is "a program that can 'infect' other programs by modifying them to include a possibly evolved copy of itself". Detection by appearance is undecidable.
  • Four phases: dormant (idle), propagation (copies itself), triggering (condition met), execution (the payload).
  • Structure when the host runs: check the target is not already infected (a marker), attach a copy, act if triggered, hand control back so nothing looks wrong.
  • Compression defeats a length check: squeeze the host by more than the addition needs.
  • NIST's kinds: compiled (file infector, boot sector, multipartite) and interpreted (macro, scripting).
  • Concealment: encrypted (masked body, fixed routine), stealth (lies to reads), polymorphic (routine rewritten too), metamorphic (whole body rewritten).
  • Run: length catches 1 of 4, a body signature 1 of 4, a routine signature 2 of 4, a stored hash all 3 changed files.
munotes.in594

Viruses: The Four Phases, the Structure and the Types

Test yourself

1. Define a virus and describe its four phases. A virus is a program that infects other programs by modifying them to include a possibly evolved copy of itself, so that the copy runs and infects further programs when the modified program is run. In the dormant phase it is idle, waiting for an event such as a date or the presence of a file to activate it. In the propagation phase it places copies of itself into other programs or into system areas of a disk. In the triggering phase the condition it waits for is met and it prepares to act. In the execution phase its payload is performed, which may be a harmless message or the destruction of data.

2. Describe what a virus attached to a program does when that program is run, and why each step is there. It first checks whether the program it has chosen is already infected, usually by looking for a marker it left before, so that it does not attach twice and make the file grow twice. It then attaches a copy of itself to that program. If its trigger condition is met, it performs its payload. Finally it transfers control to the original program so that the program behaves exactly as the user expects and nothing appears wrong. Each step is arranged so that some check, on file size, on behaviour, or by the user, fails to notice.

3. How does a compression virus conceal itself, and what does that tell you about detection? It compresses the host program before attaching itself, by more than the space its own copy requires, and pads the result so that the infected file has exactly the same length as before; when the program is run, the original is expanded and executed normally. It tells you that a check on file length alone is not enough, and more generally that each concealment technique is aimed at one particular check, so defences must be layered.

4. Distinguish encrypted, polymorphic and metamorphic viruses. In an encrypted virus most of the body is stored masked under a key that differs between copies, with a small constant routine that unmasks it before it runs; a signature taken from the body fails, but one taken from the constant routine succeeds. A polymorphic virus masks the body and also rewrites the unmasking routine for every copy, so no fixed sequence of bytes survives and signature scanning fails entirely. A metamorphic virus rewrites its whole body on each copy, so two copies may share no bytes and may even behave differently while achieving the same effect.

5. Why can no scanner detect every virus? Because, as Cohen showed, deciding whether an arbitrary program is a virus is undecidable: given any decision procedure, one can describe a program that consults it and infects others precisely when the procedure says it is not a virus, which makes the procedure self contradictory. Detection by appearance, by behaviour, and detection of an evolution of a known virus are all undecidable, so scanners can only recognise what is already known, and defence must also use checks that do not depend on recognising the attacker, such as integrity checking against stored hashes.

Contents This chapter on its own page

munotes.in595

Chapter Eighty-Nine

Worms, and the Ones That Made History

Syllabus topic Module 2, "Malicious Software: Viruses and Related Threats"

In one line

A worm is a program that copies itself to other machines by itself, so its speed is limited by the network rather than by people, and the fastest of them infected most of the vulnerable machines on the Internet in about ten minutes.

In the words an answer should use: a worm is, in NIST's definition, "a self-replicating, self-contained program that usually executes itself without user intervention". It uses network connections to spread from system to system: network service worms exploit a flaw in a service to propagate, and mass mailing worms send themselves as email. A worm performs the same four phases as a virus, dormant, propagation, triggering and execution, but its propagation phase is different: it searches for other systems to infect, establishes a connection to a chosen system, and copies itself there, where the copy begins again.

How a worm spreads

A worm's propagation phase has three steps, repeated for every target. It searches for other systems, using whatever lists the infected machine holds, host tables, address books, lists of trusted machines, or simply by generating addresses at random. It connects to one it has found. And it copies itself across and starts the copy running.

Each step is a place to stop it. The search shows up as an unusual number of connection attempts; the connection can be refused by a firewall; and the copy has to exploit something, which a patch can close.

The five that are worth knowing

Morris, 2 November 1988

The first worm to reach the whole Internet. RFC 1135 records it as unleashed "the evening of 2 November 1988", attacking Sun workstations and VAXes running Berkeley Unix. It used four ways in: a non-standard debug command in sendmail; an overflow in fingerd, where too many characters were sent for the gets library routine to hold, so that the worm "was able to execute a small arbitrary program"; the trusted host features of local networks, through rexec and rsh, following /etc/hosts.equiv and .rhosts files; and password guessing, "attempting to access accounts with obvious passwords".

What it taught. Four lessons, all still current. Input that is longer than the buffer holding it is a way in. Trust between machines spreads a compromise along with it. Weak passwords open accounts. And, as the chapters on incident response reflect, somebody has to coordinate the answer: the lessons RFC 1135 lists include that "Connectivity was important" and that late-night authentication of the person on the telephone is a real problem. CERT/CC was created in the aftermath.

Code Red, 19 July 2001

A network service worm against Microsoft's IIS web server. CAIDA measured it: "more than 359,000 computers connected to the Internet were infected with the Code-Red (CRv2) worm in less than 14 hours", and its infection rate "peaked at over 2,000 hosts per minute".

munotes.in596

Worms, and the Ones That Made History

What it taught. Two things. The first version chose the addresses it probed with "a static seed in its random number generator and thus generates identical lists of IP addresses on each infected machine", so every copy scanned the same machines in the same order and it spread slowly; the version that seeded properly spread explosively. And CAIDA's conclusion, which is the lesson for a defender: the episode "demonstrates that wide-spread vulnerabilities in Internet hosts can be exploited quickly and dramatically, and that techniques other than host patching are required to mitigate Internet worms".

Nimda, 18 September 2001

CERT/CC's advisory CA-2001-26 lists five ways it spread, which is why it is remembered:

  • from client to client by email;
  • from client to client over open network shares;
  • from a web server to a client, when someone browsed a compromised site;
  • from a client to a web server, by scanning for directory traversal flaws in IIS;
  • from a client to a web server, by scanning for the back doors left behind by the earlier Code Red II and sadmind worms.

Its email arrived as a message that "appears to have no content", carrying an attachment named readme.exe declared as audio, which a vulnerable mail program ran automatically on being opened or previewed.

What it taught. A worm with several ways in cannot be stopped by closing one of them: mail filtering, patching the web server and closing the shares all had to happen. It also showed that one worm's leftovers are the next worm's front door.

Slammer, or Sapphire, 25 January 2003

The fastest. CAIDA's measurements: it "doubled in size every 8.5 seconds", "infected more than 90 percent of vulnerable hosts within 10 minutes", and reached "over 55 million scans per second" across the Internet in under three minutes, infecting "at least 75,000 hosts". Its consequences reached far outside computing: "canceled airline flights, interference with elections, and ATM failures".

Why it was so fast. It was tiny, "a total size of only 376 bytes", so its whole self fitted in one UDP packet of 404 bytes, against "the 4kb size of Code Red, or the 60kb size of Nimda". And because UDP needs no reply, it never waited: Code Red "was latency limited", each thread waiting for a connection to answer or time out, while Slammer "was bandwidth-limited, allowing it to scan as fast as the compromised computer could transmit packets".

What it taught. That human response time is not fast enough. CAIDA's own reflection is that if such a worm had stopped scanning once it was finished, "it would likely take hours or days of effort simply to identify the attack". The patch had been available since before the flaw was even announced.

munotes.in597

Worms, and the Ones That Made History

WannaCry, 12 May 2017

Ransomware that spread like a worm. CISA's alert records it "discovered the morning of May 12, 2017", spreading "over several hours" to "hundreds of thousands of infections in over 150 countries", asking a ransom of ".1781 bitcoins, roughly $300". It spread "via the MS17-010/EternalBlue SMBv1.0 exploit", and, as the alert notes, "Microsoft released a security update for the MS17-010 vulnerability on March 14, 2017", eight weeks earlier.

What it taught. That worms did not end with the 1990s; that the payload had become extortion; and that a patch nobody installs is not a defence. It is also why hospitals, railways and factories now treat patching as an operational duty rather than an IT convenience.

The run: what those numbers mean

CAIDA models a scanning worm as random constant spread: each infected host scans random addresses at a constant rate, so infections grow exponentially while targets are plentiful and level off as they run out. The listing works that model against the published figures. Nothing here scans anything; it is arithmetic.

# How fast a scanning worm spreads, worked from the figures its measurers published.
# The model is CAIDA's "random constant spread": each infected host scans random addresses,
# so infections grow exponentially at first and level off as targets run out.
import datetime, math

SPACE = 2 ** 32                      # the IPv4 address space a random scanner draws from

def rate_constant(scans_per_second, vulnerable):
    """Chance per second that one infected host finds one more victim, early on."""
    return scans_per_second * vulnerable / SPACE

def doubling(k):
    return math.log(2) / k

def infected_after(seconds, k, vulnerable, start=1):
    """The logistic curve the model gives."""
    growth = math.exp(k * seconds)
    return vulnerable * start * growth / (vulnerable + start * (growth - 1))

# ---- 1. what scan rate the measured doubling time implies ------------------------------------
VULNERABLE = 75_000                  # "at least 75,000 hosts", CAIDA on Slammer
MEASURED_DOUBLING = 8.5              # seconds, "8.5 (+/-1) seconds" in the first minute
k = math.log(2) / MEASURED_DOUBLING
needed = k * SPACE / VULNERABLE
print('1. SLAMMER, 25 JANUARY 2003: WHAT 8.5 SECONDS MEANS')
print('   vulnerable hosts %s, address space %s' % (format(VULNERABLE, ','), format(SPACE, ',')))
print('   a scan finds a victim once in %s tries' % format(SPACE // VULNERABLE, ','))
print('   to double every %.1f s, each infected host must scan about %s addresses a second'
      % (MEASURED_DOUBLING, format(round(needed, -2), ',')))
print('   at 404 bytes a packet that is %.1f Mbit/s from every infected machine'
      % (needed * 404 * 8 / 1e6))

# ---- 2. the exponential phase demands more bandwidth than the Internet had -------------------
PEAK_MEASURED = 55_000_000           # scans a second, the whole worm at its peak
demand = VULNERABLE * needed
print()
print('2. WHY THE GROWTH SLOWED')
print('   all %s hosts scanning at that rate: %s scans a second' % (format(VULNERABLE, ','),
                                                                    format(round(demand), ',')))
print('   the peak actually measured:         %s scans a second' % format(PEAK_MEASURED, ','))
print('   the worm asked for %.1f times the scanning the network would carry'
      % (demand / PEAK_MEASURED))

# ---- 3. how long a random scanner needs to cover the address space ---------------------------
print()
print('3. COVERAGE AT THE MEASURED PEAK RATE (a random scanner repeats itself)')
for minutes in (1, 3, 10, 30):
    scans = PEAK_MEASURED * minutes * 60
    covered = 1 - math.exp(-scans / SPACE)
    print('   after %2d minutes: %s scans, %.1f%% of all addresses tried at least once'
          % (minutes, format(scans, ','), 100 * covered))

# ---- 4. Slammer against Code Red, from each one's own measured figures ------------------------
print()
print('4. TWO WORMS, EACH FROM ITS MEASURERS\' FIGURES')
CODE_RED = (359_000, 14 * 3600, 'Code Red, 19 July 2001')
SLAMMER = (VULNERABLE, 10 * 60, 'Slammer, 25 January 2003')
for hosts, seconds, label in (CODE_RED, SLAMMER):
    doublings = math.log2(hosts)
    print('   %-28s %s hosts in %s: %.1f doublings, one every %s'
          % (label, format(hosts, ','),
             '%d hours' % (seconds / 3600) if seconds >= 3600 else '%d minutes' % (seconds / 60),
             doublings, ('%.0f minutes' % (seconds / doublings / 60)) if seconds / doublings > 60
             else '%.1f seconds' % (seconds / doublings)))

# ---- 5. the window that was open, and how long it had been open ------------------------------
print()
print('5. THE PATCH WAS ALREADY OUT')
EVENTS = [('Slammer', 'CVE-2002-0649 published', datetime.date(2002, 8, 12),
           datetime.date(2003, 1, 25)),
          ('WannaCry', 'MS17-010 released   ', datetime.date(2017, 3, 14),
           datetime.date(2017, 5, 12))]
for name, what, known, struck in EVENTS:
    print('   %-9s %s %s, worm %s: %3d days in between'
          % (name, what, known, struck, (struck - known).days))
print('   and Microsoft had shipped the SQL Server fix even before that flaw was announced')
munotes.in598

Worms, and the Ones That Made History

1. SLAMMER, 25 JANUARY 2003: WHAT 8.5 SECONDS MEANS
   vulnerable hosts 75,000, address space 4,294,967,296
   a scan finds a victim once in 57,266 tries
   to double every 8.5 s, each infected host must scan about 4,700.0 addresses a second
   at 404 bytes a packet that is 15.1 Mbit/s from every infected machine

2. WHY THE GROWTH SLOWED
   all 75,000 hosts scanning at that rate: 350,240,526 scans a second
   the peak actually measured:         55,000,000 scans a second
   the worm asked for 6.4 times the scanning the network would carry

3. COVERAGE AT THE MEASURED PEAK RATE (a random scanner repeats itself)
   after  1 minutes: 3,300,000,000 scans, 53.6% of all addresses tried at least once
   after  3 minutes: 9,900,000,000 scans, 90.0% of all addresses tried at least once
   after 10 minutes: 33,000,000,000 scans, 100.0% of all addresses tried at least once
   after 30 minutes: 99,000,000,000 scans, 100.0% of all addresses tried at least once

4. TWO WORMS, EACH FROM ITS MEASURERS' FIGURES
   Code Red, 19 July 2001       359,000 hosts in 14 hours: 18.5 doublings, one every 46 minutes
   Slammer, 25 January 2003     75,000 hosts in 10 minutes: 16.2 doublings, one every 37.0 seconds

5. THE PATCH WAS ALREADY OUT
   Slammer   CVE-2002-0649 published 2002-08-12, worm 2003-01-25: 166 days in between
   WannaCry  MS17-010 released    2017-03-14, worm 2017-05-12:  59 days in between
   and Microsoft had shipped the SQL Server fix even before that flaw was announced
munotes.in599

Worms, and the Ones That Made History

What the run establishes, in order.

Doubling every 8.5 seconds is a demand for bandwidth. With 75,000 vulnerable machines in an address space of 4.3 billion, a random scan finds a victim about once in 57,000 tries, so to double every 8.5 seconds each infected machine must send about 4,700 scans a second, which at 404 bytes a packet is about 15 Mbit/s from every one of them. That is the meaning of bandwidth-limited.

The worm outgrew the network, which is why it slowed. All 75,000 hosts scanning at that rate would need about 350 million scans a second; the peak actually measured was 55 million, about six times less. CAIDA saw the same thing: growth slowed because "significant portions of the network did not have enough bandwidth to allow it to operate unhindered", and some networks shut down under the load.

A random scanner covers the address space quickly but repeats itself. At 55 million scans a second, 90 per cent of all addresses have been tried at least once after about three minutes. CAIDA's page puts this at "a little more than 10 minutes"; the figure that matters, and the one measured rather than modelled, is theirs: most vulnerable machines were infected within ten minutes.

The gap between the two worms is the whole story of the period. Code Red's 359,000 hosts in under 14 hours is a doubling about every 46 minutes; Slammer's 75,000 in 10 minutes is a doubling every 37 seconds. A defence that depends on somebody noticing, deciding and acting can work against the first and cannot work against the second.

Both had a patch. The flaw Slammer used was published in NIST's vulnerability database on 12 August 2002, 166 days before the worm, and CAIDA records that Microsoft's fix came out even before the flaw was announced. The flaw WannaCry used was patched on 14 March 2017, 59 days before. Neither worm defeated a defence; both found machines where the defence had not been applied.

munotes.in600

Worms, and the Ones That Made History

Distinctions that carry marks

VirusWorm
Needs a host programyesno
Spread depends ona person running or opening somethingthe network and the worm's own scanning
Speeddays to weeksminutes
Main defenceantivirus, care with filespatching, firewalls, blocking unused services
Code RedSlammer
Date19 July 200125 January 2003
Sizeabout 4 kB376 bytes, one UDP packet
Limited bylatency, waiting for connectionsbandwidth, the link's capacity
Doublingabout 46 minutes8.5 seconds
Hostsmore than 359,000at least 75,000

What beginners get wrong here

Saying a worm needs a user to open something. That is the virus, and the mass mailing worm; a network service worm arrives at a listening service and needs nobody.

Thinking the biggest worm was the fastest. Code Red infected far more machines than Slammer and spread hundreds of times more slowly; size of population and speed of spread are different things.

Giving the Morris worm one method. RFC 1135 lists sendmail's debug command, the fingerd overflow, trusted-host relationships, and password guessing.

Treating WannaCry as only ransomware. Its payload was ransomware; what made it an emergency was that it spread by itself through a network service.

Saying these attacks were unpatchable. For both Slammer and WannaCry the fix had been published, months and weeks earlier.

Quick revision

  • Worm: self-replicating, self-contained, spreads without a user. Propagation: search, connect, copy and run.
  • Morris, 2 Nov 1988: sendmail debug, fingerd overflow, rexec and rsh trusted hosts, password guessing. CERT/CC followed.
  • Code Red, 19 July 2001: IIS; 359,000 hosts in under 14 hours; the first version's static seed crippled it.
  • Nimda, 18 Sept 2001: five mechanisms, including back doors left by earlier worms.
  • Slammer, 25 Jan 2003: 376 bytes, one UDP packet; doubling every 8.5 s; 90 per cent of vulnerable hosts in 10 minutes; bandwidth-limited, not latency-limited.
  • WannaCry, 12 May 2017: ransomware over MS17-010 SMBv1; patch out since 14 March 2017; 150 countries.
  • Random constant spread: exponential at first, levelling as targets run out; speed is scan rate times the density of targets.

Test yourself

1. What is a worm, and how does its propagation phase work? A worm is a self-replicating, self-contained program that usually runs without user intervention and uses network connections to spread from system to system. In its propagation phase it searches for other systems to infect, using host tables, address books, lists of trusted machines or randomly generated addresses; establishes a connection with a system it has found; and copies itself to it and causes the copy to run, whereupon the copy begins searching in turn.

munotes.in601

Worms, and the Ones That Made History

2. Describe the Morris worm and what it exploited. Released on the evening of 2 November 1988, it attacked Sun workstations and VAXes running Berkeley Unix. It used a non-standard debug command in sendmail; an overflow in the finger daemon, where more characters were supplied than the gets routine could hold, letting the worm run code of its own; the trusted host relationships used in local networks through rexec and rsh, following hosts.equiv and .rhosts files; and guessing obvious passwords. It led to the creation of CERT/CC.

3. Why did Slammer spread so much faster than Code Red? Because of its size and its choice of protocol. Slammer was 376 bytes, so the whole worm travelled in one 404-byte UDP packet, against about 4 kB for Code Red. Code Red opened TCP connections and each of its threads had to wait for an answer or a timeout, so it was limited by network latency; Slammer needed no reply and could send scans as fast as the machine and its link allowed, so it was limited only by bandwidth. It therefore doubled every 8.5 seconds and infected more than 90 per cent of vulnerable hosts within ten minutes, while Code Red doubled about every 46 minutes.

4. What did Nimda demonstrate? That a worm using several routes at once cannot be stopped by closing one. CERT/CC recorded five mechanisms: email from client to client; open network shares; infection of clients browsing compromised web servers; scanning by clients for directory traversal flaws in IIS servers; and scanning for the back doors left behind by the earlier Code Red II and sadmind worms. That last one showed that the remains of one worm become the entry point of the next.

5. What do Slammer and WannaCry together say about patching? That in both cases the defence existed and had not been applied. The flaw Slammer used was recorded in the national vulnerability database on 12 August 2002, and Microsoft's fix was released even before the flaw was announced, yet the worm spread 166 days later; the flaw WannaCry used was patched on 14 March 2017 and the worm spread 59 days later. Neither worm broke a defence, so the practical lesson is that patching is an operational duty with a deadline, and that because a fast worm outruns human response, defences that do not depend on someone noticing, such as removing unused services and blocking them at the firewall, matter as well.

Contents This chapter on its own page

munotes.in602

Chapter Ninety

Virus Countermeasures

Syllabus topic Module 2, "Malicious Software: Virus Countermeasures"

In one line

No single defence finds everything, so the answer is layers: keep the malware out (patching, least privilege, filtering), find what gets in (scanning, emulation, integrity checks), and stop what is found from doing harm (behaviour blocking, quarantine, backups).

In the words an answer should use: the ideal solution to the threat of viruses is prevention, but prevention is rarely achieved, so the realistic approach is detection, identification and removal. Antivirus software has developed through four generations: first generation, simple scanners that need a virus signature and can also check a program's length; second generation, heuristic scanners, which look for fragments of code often associated with viruses or use integrity checking with checksums; third generation, activity traps, memory-resident programs that identify a virus by the actions it takes rather than its structure; and fourth generation, full-featured protection, packages that combine scanning and activity traps with access controls that limit the ability of viruses to reach files at all.

Prevention, and why it is first

NIST's guide organises prevention into five kinds of measure, and every one of them removes work from the scanner.

MeasureExamples from SP 800-83
Policywhat may be installed, what may be attached to email, who may use removable media
Awarenessteaching people to recognise phishing and social engineering, which is how most malware arrives
Vulnerability mitigationpatching, and least privilege, because "malware often requires administrator-level privileges to exploit vulnerabilities successfully"
Threat mitigationantivirus, intrusion prevention, firewalls, content filtering, application whitelisting
Defensive architecturesandboxing, keeping browsers separate, protecting the firmware

Least privilege is the cheapest of these. A student account that cannot write to program directories cannot have its programs infected by anything it runs, whatever the antivirus misses.

The four generations of antivirus

This is the textbook's classification of how antivirus software developed.

GenerationCalledWorks byBeaten by
Firstsimple scannermatching a known signature; checking a program's lengthany variant; the compression trick
Secondheuristic scannerlooking for fragments and structures typical of viruses; integrity checking, storing a checksum of each file and recomparingan attacker who can also update the stored checksums; encrypted checksums answer that
Thirdactivity trapstaying in memory and watching for actions typical of a virus, rather than structurenothing structural; but legitimate programs take those actions too
Fourthfull-featured protectioncombining scanning and activity traps with access control, so that malware cannot reach what it wantspoor configuration, and users who approve whatever they are asked

Generic decryption is the answer to the encrypted and polymorphic viruses of the previous chapters. The scanner runs the suspect file inside an emulator, a sealed interpreter where nothing it does touches the real machine, and waits: a masked virus must unmask itself before it can run, and the moment it does, the scanner recognises the body. The difficulty is knowing how long to wait, because the scanner must not spend too long on a file that is simply slow to start.

munotes.in603

Virus Countermeasures

The digital immune system is the textbook's account of a proposal from IBM: when a machine finds something suspicious, it sends the sample to a central analysis machine, which runs it in an emulated environment, works out how to recognise and remove it, and sends the answer back to every machine on the network, so that one victim protects the rest. The idea survives today in the way antivirus products send suspicious files to their makers' analysis systems and receive new definitions within hours.

Behaviour-blocking software is the fourth generation's core: it runs alongside the operating system and watches what a program does, an attempt to open a file for writing, to change system settings, to open a network connection, and blocks or asks before the action completes. Its advantage over a scanner is that it does not need to have seen the malware before. Its cost is that legitimate software does many of those things too, which is the false-alarm problem the run measures.

The run: four defences on the same files

The listing takes eight files, five of them harmful, and applies four defences: a scanner looking for known bytes, an emulating scanner, an integrity check against the hash each file had when it was installed, and a behaviour blocker that flags any program that writes to other programs. Nothing runs: the samples are byte strings, and every figure in the summary is counted by the program.

# Four defences measured against the same set of files, and then used together.
# Everything here is a detector: the "samples" are ordinary byte strings that never run.
import hashlib

SIGNATURE = b'\x8b\x45\xfc\x83\xc0\x01\x89\x45\xfc\xeb\xe2'   # the bytes a scanner was given
STUB = b'\x31\xc9\xb1\x0b\x80\x34\x0e'                         # a fixed unmasking loop

def masked(data, key):
    return bytes(b ^ key for b in data)

PROGRAM = b'GRADES REPORT v2\x00' + bytes(range(64)) * 3
UPDATER = b'COLLEGE UPDATER v4\x00' + bytes(range(48)) * 2
# name -> (as installed, as it is now, is it harmful, does it write to other programs)
SAMPLES = {
    'notes.exe':     (PROGRAM, PROGRAM, False, False),
    'report.exe':    (PROGRAM + b'\x00' * 32, PROGRAM + b'\x00' * 32, False, False),
    'setup.exe':     (PROGRAM, PROGRAM + SIGNATURE, True, True),
    'game.exe':      (PROGRAM, PROGRAM + STUB + masked(SIGNATURE, 0x11), True, True),
    'demo.exe':      (PROGRAM, PROGRAM + STUB + masked(SIGNATURE, 0x7d), True, True),
    'tool.exe':      (PROGRAM, PROGRAM + b'\x33\xc9\xb9\x0b' + masked(SIGNATURE, 0x42), True, True),
    'macro.doc':     (PROGRAM, PROGRAM + b'\x90' * 40, True, True),
    'patcher.exe':   (UPDATER, UPDATER, False, True),          # writes to programs for a living
}
NOTE = {'setup.exe': 'carries known bytes', 'game.exe': 'the same, masked',
        'demo.exe': 'masked, another key', 'tool.exe': 'masked, stub rewritten',
        'macro.doc': 'never seen before', 'patcher.exe': 'a real updater',
        'notes.exe': 'untouched', 'report.exe': 'untouched'}

# ---- the four defences ------------------------------------------------------------------------
def simple_scanner(blob):
    """First generation: does the file contain a sequence of bytes on the list?"""
    return SIGNATURE in blob

def emulating_scanner(blob):
    """Generic decryption: let the suspect run in a sealed interpreter until it reveals its
    own body, then scan what was revealed. Here: undo what the fixed stub would undo."""
    if simple_scanner(blob):
        return True
    if STUB not in blob:
        return False
    body = blob[blob.index(STUB) + len(STUB):]
    return any(SIGNATURE in masked(body, key) for key in range(256))

def integrity_check(installed, now):
    """Has this file changed since it was installed and its hash written down?"""
    return hashlib.sha256(now).hexdigest() != hashlib.sha256(installed).hexdigest()

def behaviour_blocker(writes_to_programs):
    """Fourth generation: watch what a program DOES, and stop it before harm is done."""
    return writes_to_programs

DEFENCES = ('scanner', 'emulating', 'integrity', 'behaviour')
print('WHAT EACH DEFENCE SEES')
print('   %-13s %-22s %-5s %-8s %-9s %-9s %s'
      % ('file', 'what it is', 'bad?', *DEFENCES))
score = {d: [0, 0] for d in DEFENCES}
for name, (installed, now, harmful, writes) in SAMPLES.items():
    verdict = {'scanner': simple_scanner(now), 'emulating': emulating_scanner(now),
               'integrity': integrity_check(installed, now), 'behaviour': behaviour_blocker(writes)}
    for d in DEFENCES:
        score[d][0 if harmful else 1] += verdict[d]
    print('   %-13s %-22s %-5s %-8s %-9s %-9s %s'
          % (name, NOTE[name], 'yes' if harmful else 'no',
             *['FLAG' if verdict[d] else '-' for d in DEFENCES]))

harmful_total = sum(1 for v in SAMPLES.values() if v[2])
clean_total = len(SAMPLES) - harmful_total
print()
print('   %-11s %-22s %s' % ('defence', 'harmful caught', 'clean wrongly flagged'))
for d in DEFENCES:
    print('   %-11s %-22s %s' % (d, '%d of %d' % (score[d][0], harmful_total),
                                 '%d of %d' % (score[d][1], clean_total)))

together = [0, 0]
for name, (installed, now, harmful, writes) in SAMPLES.items():
    flagged = (emulating_scanner(now) or integrity_check(installed, now)
               or behaviour_blocker(writes))
    together[0 if harmful else 1] += flagged
print('   %-11s %-22s %s' % ('all four', '%d of %d' % (together[0], harmful_total),
                             '%d of %d' % (together[1], clean_total)))
print()
print('   the one clean file still flagged is the updater, which writes to other programs')
print('   because that is its job: somebody has to decide that, once, and record the decision')
munotes.in604

Virus Countermeasures

WHAT EACH DEFENCE SEES
   file          what it is             bad?  scanner  emulating integrity behaviour
   notes.exe     untouched              no    -        -         -         -
   report.exe    untouched              no    -        -         -         -
   setup.exe     carries known bytes    yes   FLAG     FLAG      FLAG      FLAG
   game.exe      the same, masked       yes   -        FLAG      FLAG      FLAG
   demo.exe      masked, another key    yes   -        FLAG      FLAG      FLAG
   tool.exe      masked, stub rewritten yes   -        -         FLAG      FLAG
   macro.doc     never seen before      yes   -        -         FLAG      FLAG
   patcher.exe   a real updater         no    -        -         -         FLAG

   defence     harmful caught         clean wrongly flagged
   scanner     1 of 5                 0 of 3
   emulating   3 of 5                 0 of 3
   integrity   5 of 5                 0 of 3
   behaviour   5 of 5                 1 of 3
   all four    5 of 5                 1 of 3

   the one clean file still flagged is the updater, which writes to other programs
   because that is its job: somebody has to decide that, once, and record the decision
munotes.in605

Virus Countermeasures

What the run establishes, in order.

A plain scanner finds only what it has been given. One of five, the file carrying the exact bytes on its list. It raises no false alarms at all, which is why scanning remains the first line: it is cheap and quiet.

Emulation recovers the masked ones. Three of five, because unmasking is something the file must do to itself. The one that rewrote its unmasking routine defeats it here, and so does the one nobody has seen before.

Integrity checking finds every changed file and accuses nobody. Five of five with no false alarms, because it compares each file against its own hash from installation and asks nothing about what malware looks like. Its two costs are that the baseline must be recorded before the trouble starts, and that it must be stored where an attacker cannot rewrite it.

Behaviour blocking finds everything and argues about the updater. Five of five, and one false alarm: a legitimate updater writes to other programs, which is its job. That is the permanent trade of behaviour-based defence, and it is the base-rate problem of the intrusion-detection chapters in a new place: the answer is not a cleverer rule but a decision recorded once, that this updater is allowed to do this.

Layers work because their blind spots differ. All four together catch all five, and the only disagreement left is a question for a person, not for a program.

What a college laboratory should actually do

For a laboratory of ordinary Windows or Linux machines, in the order that gives the most safety for the least effort:

  1. Patch automatically. Both worms in the previous chapter had patches out for months or weeks.
  2. Take away administrator rights from the accounts students use.
  3. Keep antivirus on and updated, configured as NIST describes: on-access scanning of files as they are opened, regular full scans, and scanning of removable media before use.
  4. Filter at the gateway so that executable attachments and known bad sites do not reach the machines.
  5. Restore, do not clean. A laboratory machine should be rebuilt from a known good image rather than disinfected; the image is the integrity baseline.
  6. Back up what cannot be rebuilt, offline, and test a restore. Against ransomware this is the only defence that always works.
  7. Report. In India CERT-In's directions require reporting the listed incidents within six hours, and keeping 180 days of logs.
munotes.in606

Virus Countermeasures

Distinctions that carry marks

Signature scanningIntegrity checkingBehaviour blocking
Asksdoes this look like known malware?has this file changed?what is this program doing?
Finds new malwarenoyes, once it changes somethingyes
False alarmsvery fewfew, if the baseline is rightmany, from legitimate software
Needsa signature list, updateda trusted baseline, protectedrules about what is allowed
When it actsbefore the file runsafter the change, before it runsas the harm is attempted
DetectionIdentificationRemoval
Questionis something wrong?which malware is it?put the system back as it was
If it failsthe infection continuesthe right removal is unknownthe file must be replaced from backup

What beginners get wrong here

Treating antivirus as the whole defence. It is one of five kinds of measure in NIST's list, and the cheapest wins, patching and least privilege, come before it.

Thinking a checksum must be secret. It must be correct and protected from being rewritten; encrypted or signed checksums exist so that an attacker who changes a file cannot also change its recorded hash.

Expecting behaviour blocking to be quiet. It flags legitimate updaters and installers; a defence that watches actions must be tuned, and the tuning is a decision somebody records.

Believing disinfection always works. NIST notes that "many infected files cannot be disinfected", and says software should quarantine or delete what it cannot clean; for a laboratory, rebuilding from an image is better still.

Quick revision

  • Ideal is prevention; in practice detection, identification, removal.
  • NIST's prevention: policy, awareness, vulnerability mitigation (patching, least privilege), threat mitigation (antivirus, IPS, firewalls, filtering, whitelisting), defensive architecture (sandboxing, browser separation).
  • Four generations: simple scanner (signature, length), heuristic scanner (fragments, integrity checking), activity trap (actions, memory-resident), full-featured (scanning plus traps plus access control).
  • Generic decryption: run the suspect in an emulator until it unmasks itself, then scan.
  • Digital immune system: sample sent to a central analyser, a cure derived and distributed to everyone.
  • Behaviour blocking: watches actions, catches the unknown, argues with legitimate updaters.
  • Run: scanner 1 of 5, emulation 3 of 5, integrity 5 of 5 with no false alarms, behaviour 5 of 5 with one.
  • A laboratory: patch, no administrator rights, antivirus on-access, gateway filtering, rebuild from images, offline backups, report to CERT-In in six hours.

Test yourself

1. Describe the four generations of antivirus software. The first generation is the simple scanner, which requires a virus signature to identify it and may also record and check program lengths; it can only find viruses that are already known. The second generation is the heuristic scanner, which does not rely on a specific signature but looks for fragments of code and structures typical of viruses, or uses integrity checking, storing a checksum for each file and recomputing it to detect change. The third generation is the activity trap, a memory-resident program that identifies a virus by the actions it takes rather than by its structure. The fourth generation is full-featured protection, packages that combine scanning and activity traps with access control so that malware cannot reach the files it wants.

munotes.in607

Virus Countermeasures

2. What is generic decryption, and what problem does it solve? It answers encrypted and polymorphic viruses, whose body is masked differently in every copy so that no fixed signature matches. The file is run inside an emulator, a controlled interpreter in which nothing it does affects the real machine. Because the virus must unmask its own body before it can run, it reveals itself, and the scanner then matches the revealed body against its signatures. The practical difficulty is deciding how long to let a file run before concluding it is clean.

3. Explain behaviour-blocking software, with its advantage and its weakness. Behaviour-blocking software is integrated with the operating system and monitors the actions programs take, such as opening files for writing, altering system settings, formatting disks or opening network connections, and blocks or queries actions that violate policy before they complete. Its advantage is that it can stop malware nobody has seen before, because it judges behaviour rather than appearance. Its weakness is that legitimate programs, updaters and installers in particular, take the same actions, so it raises false alarms and requires decisions about what is permitted.

4. What is a digital immune system? It is the approach in which a machine that detects suspicious behaviour sends the sample to a central administrative machine, which runs it in an emulated environment to analyse its behaviour, derives a way to recognise and remove it, and distributes that to every machine on the network, so that the experience of one victim immunises the rest. Antivirus products today work in much the same way, sending suspicious files to their makers and receiving updated definitions.

5. What would you do to protect a college computer laboratory from malware? Apply operating system and application patches automatically, since recent worms exploited flaws whose patches were already published; remove administrator rights from the accounts students use, since much malware needs them; run antivirus software configured for on-access scanning, regular full scans and scanning of removable media; filter executable attachments and known bad sites at the gateway; rebuild machines from a known good image rather than trying to disinfect them; keep offline backups of anything that cannot be rebuilt and test restoring them, which is the only reliable answer to ransomware; and report incidents to CERT-In within six hours, keeping 180 days of logs as its directions require.

Contents This chapter on its own page

munotes.in608

Chapter Ninety-One

Denial of Service and Distributed Denial of Service

Syllabus topic Module 2, "DDOS"

In one line

A denial of service attack takes away a service rather than taking data from it, and it is cheap because the attacker only has to exhaust whichever resource is smallest: a link, a table, or a single flaw; a distributed attack adds many machines and, worse, other people's servers.

In the words an answer should use: a denial of service (DoS) attack is an attempt to prevent legitimate users of a service from using it. It attacks the availability of a system. There are three general forms: consuming network bandwidth, so that genuine traffic cannot get through; consuming system resources such as a connection table, process list or disk space; and exploiting a flaw that makes the service crash or hang. A distributed denial of service (DDoS) attack launches the same thing from many machines at once, usually a botnet of compromised computers, and often through reflection and amplification, in which the attacker sends small requests with the victim's address forged as the source so that innocent servers send large replies to the victim.

The three shapes

ShapeWhat is exhaustedExample
Bandwidththe link into the victim's networka flood of packets larger than the link can carry
Resourcesa table, a list, a pool of memorya SYN flood, which fills the table of half-open connections
A flawnothing: the service simply failsone malformed request that crashes or hangs the server

The third is the cheapest and the easiest to fix. One packet can be enough, and one patch closes it for ever, which is why the first two are what a defender usually faces.

The SYN flood

When a client opens a TCP connection it sends a SYN; the server replies with a SYN-ACK and remembers the half-open connection while it waits for the final acknowledgement. That memory is the weakness. RFC 4987 puts it exactly: the attack "takes advantage of the state retention TCP performs for some time after receiving a SYN segment", and "does not attempt to overload the network's resources or the end host's memory, but merely attempts to exhaust the backlog of half-open connections associated with a port number".

The attacker sends SYNs with source addresses "that will not generate replies to the SYN-ACKs", so the entries are never completed and sit until they time out. Meanwhile genuine connections are refused.

RFC 4987's own defences, in its order:

DefenceHowThe catch
Filteringdrop packets with forged source addressesworks only where the filtering is done
Increasing the backlogkeep more half-open entriesthe attacker sends more
Reducing the SYN-RECEIVED timerthrow entries away soonerlegitimate clients on slow paths are dropped too
Recycling the oldest half-open entrymake room by discarding the oldestunder a fast enough attack, the oldest is always genuine
SYN cachestore much less state per half-open connectionstill bounded
SYN cookiesstore no state at all: encode what is needed into the sequence number and recover it from the client's replysome TCP options cannot be carried
Firewalls and proxiesa device in front completes the handshake and passes on only real connectionsthe device becomes the target
munotes.in609

Denial of Service and Distributed Denial of Service

SYN cookies are the interesting answer because they change the shape of the problem: if the server keeps no state until the handshake completes, there is no table to exhaust.

Reflection and amplification

A reflector attack does not send anything to the victim directly. The attacker sends requests to ordinary, innocent servers, with the source address forged to be the victim's. Every reply goes to the victim, who has no way to tell them from real traffic and cannot block the senders without blocking real services.

Amplification makes it worse, because some requests provoke much larger replies. US-CERT defines the bandwidth amplification factor as "the number of UDP payload bytes that an amplifier sends to answer a request, compared to the number of UDP payload bytes of the request". Its published table includes DNS at 28 to 54, SSDP at 30.8, CharGEN at 358.8, NTP at 556.9 and Memcached at 10,000 to 51,000.

Why UDP? Because it has no handshake. A TCP connection cannot be opened with a forged source address, since the attacker never sees the server's reply; a UDP request needs no reply to be believed, so the forged address works.

How a botnet is built

A distributed attack needs many machines, and the machines are ordinary computers whose owners do not know. The pattern is the one the malware chapters describe: malware reaches the machine, by a phishing message, an unpatched service or a Trojan; it installs a bot, which NIST calls a zombie, "installed on a host to cause it to attack other hosts"; the bot contacts a command and control channel and waits; and when the controller says so, every bot begins sending at once.

The defender's lesson is that the machines in a botnet are victims. A college laboratory machine that joins a botnet is both a compromised host to be cleaned and, until it is, a participant in an attack on someone else.

The run: how little the attacker needs

The listing computes three things: how many packets a second it takes to keep a connection backlog full, what an attacker must send to deliver a large flood through amplifiers of published strength, and what ingress filtering does to the forged addresses all of that depends on. It sends nothing anywhere.

munotes.in610

Denial of Service and Distributed Denial of Service

# Why denial of service is cheap for the attacker: three pieces of arithmetic, on the numbers
# the standards and the incident reports publish. Nothing is sent anywhere.
import ipaddress, math

# ---- 1. the SYN flood: the cost is the BACKLOG, not the bandwidth ----------------------------
print('1. A SYN FLOOD COSTS WHAT THE BACKLOG COSTS (RFC 4987)')
print('   backlog  time a half-open entry is kept   SYNs a second to keep it full   that is')
SYN_ON_WIRE = 14 + 20 + 20                     # Ethernet, IPv4 and TCP headers, no payload
for backlog, timeout in ((8, 75), (128, 120), (1024, 120), (65536, 120)):
    rate = backlog / timeout
    print('   %-8s %-33s %-31s %s'
          % (format(backlog, ','), '%d seconds' % timeout, '%.1f' % rate,
             '%.1f kbit/s' % (rate * SYN_ON_WIRE * 8 / 1000)))
print('   RFC 4987: the barrage "is no larger than the backlog, minimizing the volume of')
print('   traffic the attacker must source"; typical backlogs were "a half-dozen to several dozen"')

# ---- 2. reflection and amplification: the factors somebody measured --------------------------
# Bandwidth amplification factors as published by US-CERT in alert TA14-017A, which defines
# BAF as the UDP payload bytes an amplifier sends back per UDP payload byte of the request.
BAF = [('NetBIOS', 3.8), ('SNMPv2', 6.3), ('DNS', 28), ('SSDP', 30.8), ('LDAP', 46),
       ('CharGEN', 358.8), ('NTP', 556.9), ('Memcached', 10_000)]
TARGET = 10_000_000_000                        # 10 Gbit/s arriving at the victim
print()
print('2. WHAT THE ATTACKER MUST SEND TO DELIVER 10 Gbit/s')
print('   protocol    factor      the attacker sends      hosts at 10 Mbit/s each')
for name, factor in BAF:
    needed = TARGET / factor
    print('   %-11s %-11s %-23s %s'
          % (name, format(factor, ','), '%.1f Mbit/s' % (needed / 1e6),
             format(math.ceil(needed / 1e7), ',')))
print('   without any amplifier the attacker must send the whole %d Gbit/s itself'
      % (TARGET / 1e9))

# ---- 3. all of it rests on forging the source address -----------------------------------------
CAMPUS = ipaddress.ip_network('10.21.0.0/16')
LEAVING = [('10.21.4.9', 'a real campus machine'),
           ('10.21.77.3', 'another real one'),
           ('203.0.113.7', 'claims to be the victim'),
           ('198.51.100.2', 'claims to be somewhere else'),
           ('10.21.9.1', 'a real one again')]

def allowed_out(source):
    """BCP 38, RFC 2827: a packet may leave only if its source is one of ours."""
    return ipaddress.ip_address(source) in CAMPUS

print()
print('3. INGRESS FILTERING AT THE EDGE OF THE NETWORK IT COMES FROM (RFC 2827)')
for source, what in LEAVING:
    print('   source %-14s %-30s %s'
          % (source, what, 'allowed out' if allowed_out(source) else 'DROPPED'))
forged = sum(not allowed_out(s) for s, _ in LEAVING)
print('   %d of %d packets never leave, and a reflector can only be aimed with a forged source'
      % (forged, len(LEAVING)))

# ---- 4. the three shapes of the attack ---------------------------------------------------------
print()
print('4. THE THREE SHAPES')
SHAPES = [('consume bandwidth', 'fill the link so that nothing else fits'),
          ('consume resources', 'use up a table, a process list, a backlog'),
          ('exploit a fault', 'one malformed request that crashes the service')]
for name, how in SHAPES:
    print('   %-20s %s' % (name, how))
munotes.in611

Denial of Service and Distributed Denial of Service

1. A SYN FLOOD COSTS WHAT THE BACKLOG COSTS (RFC 4987)
   backlog  time a half-open entry is kept   SYNs a second to keep it full   that is
   8        75 seconds                        0.1                             0.0 kbit/s
   128      120 seconds                       1.1                             0.5 kbit/s
   1,024    120 seconds                       8.5                             3.7 kbit/s
   65,536   120 seconds                       546.1                           235.9 kbit/s
   RFC 4987: the barrage "is no larger than the backlog, minimizing the volume of
   traffic the attacker must source"; typical backlogs were "a half-dozen to several dozen"

2. WHAT THE ATTACKER MUST SEND TO DELIVER 10 Gbit/s
   protocol    factor      the attacker sends      hosts at 10 Mbit/s each
   NetBIOS     3.8         2631.6 Mbit/s           264
   SNMPv2      6.3         1587.3 Mbit/s           159
   DNS         28          357.1 Mbit/s            36
   SSDP        30.8        324.7 Mbit/s            33
   LDAP        46          217.4 Mbit/s            22
   CharGEN     358.8       27.9 Mbit/s             3
   NTP         556.9       18.0 Mbit/s             2
   Memcached   10,000      1.0 Mbit/s              1
   without any amplifier the attacker must send the whole 10 Gbit/s itself

3. INGRESS FILTERING AT THE EDGE OF THE NETWORK IT COMES FROM (RFC 2827)
   source 10.21.4.9      a real campus machine          allowed out
   source 10.21.77.3     another real one               allowed out
   source 203.0.113.7    claims to be the victim        DROPPED
   source 198.51.100.2   claims to be somewhere else    DROPPED
   source 10.21.9.1      a real one again               allowed out
   2 of 5 packets never leave, and a reflector can only be aimed with a forged source

4. THE THREE SHAPES
   consume bandwidth    fill the link so that nothing else fits
   consume resources    use up a table, a process list, a backlog
   exploit a fault      one malformed request that crashes the service

What the run establishes, in order.

A SYN flood is not a flood. With a backlog of 128 entries held for 120 seconds, about one packet a second keeps the table permanently full: half a kilobit a second, less than a dial-up modem. Even a backlog of 65,536 needs only a few hundred kilobits a second. The attack costs what the table costs, which is why RFC 4987's defences are all about the table and not about bandwidth.

Amplifiers turn a small line into a large flood. To deliver 10 Gbit/s to a victim, an attacker with NTP amplifiers needs to send about 18 Mbit/s, which is one ordinary broadband connection; with Memcached's published factor, about 1 Mbit/s. Without amplifiers the attacker must send the whole 10 Gbit/s, which needs a large botnet. Amplification is what lets a small attacker act like a large one.

munotes.in612

Denial of Service and Distributed Denial of Service

One filter at the right place removes the whole family. Reflection needs a forged source address, and a forged source address can be stopped where the packet starts: RFC 2827 asks every network to allow out only packets whose source belongs to it. In the run, two of five packets never leave. This is the one defence that protects somebody else, which is exactly why it is a Best Current Practice that has to be argued for rather than a product that can be bought.

Distinctions that carry marks

DoSDDoS
Sourcesone machinemany, usually a botnet, or reflectors
Blocking the sourcepossiblehard: thousands of addresses, many of them innocent
Traced back tothe attackerthe bots and reflectors, which are victims too
Direct floodReflection and amplification
Packets come fromthe attacker's machinesinnocent servers
Needs forged addressesnot necessarilyyes
Attacker's bandwidthas much as the victim receivesthe victim's total divided by the factor
Stopped at source bycleaning the botsingress filtering, and not running open reflectors
SYN floodBandwidth flood
Exhauststhe backlog of half-open connectionsthe link
Attacker needsa few packets a secondas much capacity as the link
Answered bySYN cookies, SYN cache, shorter timers, filteringcapacity, filtering upstream, scrubbing

What beginners get wrong here

Thinking every denial of service is a flood. A SYN flood uses a trickle; a single malformed request can be enough.

Saying the attack comes from the reflectors. The reflectors are ordinary servers answering what looks like a normal request; the forged source address is the attack.

Believing the victim can fix reflection alone. The victim can buy capacity and filtering, but the attack is only removed where the forged packets originate, or where the reflectors are closed.

Confusing amplification with distribution. Distribution is many sources; amplification is a large reply to a small request. An attack often uses both.

Treating a bot as the enemy. The bot is somebody's compromised laptop; the controller is the attacker.

Quick revision

  • DoS attacks availability: consume bandwidth, consume resources, or exploit a flaw.
  • SYN flood: fills the backlog of half-open connections, because TCP keeps state after a SYN. Cost to the attacker is backlog divided by timeout packets a second, about one a second for a backlog of 128.
  • RFC 4987's defences: filtering, bigger backlog, shorter SYN-RECEIVED timer, recycling the oldest, SYN cache, SYN cookies (no state at all), firewalls and proxies.
  • Reflection: forge the victim's address as the source so innocent servers reply to the victim. Amplification: the reply is much bigger. BAF measured: DNS 28 to 54, SSDP 30.8, CharGEN 358.8, NTP 556.9, Memcached 10,000 upwards.
  • Works over UDP because there is no handshake to defeat the forgery.
  • DDoS adds a botnet: malware, a bot, a command and control channel, and one order. The bots are victims.
  • BCP 38 (RFC 2827) ingress filtering stops forged sources where they start.
munotes.in613

Denial of Service and Distributed Denial of Service

Test yourself

1. What is a denial of service attack? Give its three general forms. It is an attempt to prevent legitimate users from using a service, and so an attack on availability rather than on confidentiality or integrity. Its forms are consuming network bandwidth, so that genuine traffic cannot reach the victim; consuming system resources such as a connection backlog, process table or disk space; and exploiting a flaw so that the service crashes or hangs, which can need only one request.

2. Explain the SYN flood, and why it needs so little bandwidth. When a server receives a TCP SYN it replies with a SYN-ACK and keeps state for the half-open connection until the handshake completes or times out. An attacker sends SYNs with source addresses that will never answer the SYN-ACK, so the entries stay until they expire, and when the backlog is full genuine connections are refused. Because only the backlog has to be kept full, the attacker needs about as many packets per timeout period as the backlog has entries: for a backlog of 128 held for 120 seconds, roughly one packet a second, a fraction of a kilobit per second.

3. Describe reflection and amplification, and why they use UDP. In a reflector attack the attacker sends requests to ordinary servers with the source address forged to be the victim's, so that the replies all go to the victim, who cannot distinguish or safely block them. Amplification adds the choice of a request whose reply is far larger: US-CERT's published factors include 28 to 54 for DNS, 556.9 for NTP and 10,000 upwards for Memcached, so a small line can deliver a very large flood. They use UDP because it has no handshake, so a forged source address is never tested; over TCP the attacker would have to see the server's reply to continue.

4. How is a botnet built and used for a DDoS attack? Malware reaches ordinary computers by phishing, unpatched network services or Trojan software, and installs a bot, a program that makes the host attack others on command. Each bot contacts a command and control channel and waits. When the controller gives the order, all of them send traffic to the victim at once, which both multiplies the bandwidth available to the attacker and hides the attacker among thousands of innocent owners whose machines are themselves victims.

5. What is ingress filtering, and why is it described as protecting other people? Ingress filtering, defined in RFC 2827 as Best Current Practice 38, means a network allows a packet out only if its source address is one the network owns. It therefore prevents machines on that network from forging source addresses, which is what reflection attacks and many floods depend on. The network that applies it gains little itself: what it prevents is its own machines being used to attack somebody else, which is why it has to be adopted as a shared duty rather than bought as a product.

Contents This chapter on its own page

munotes.in614

Chapter Ninety-Two

DDoS Countermeasures

Syllabus topic Module 2, "DDoS countermeasures"

In one line

There are three moments at which a flood can be met: before it starts, by not letting forged addresses and open reflectors exist; while it runs, by noticing quickly; and after it is noticed, by getting the traffic filtered upstream, because once the packets have crossed your link the room is already gone.

In the words an answer should use: countermeasures against denial of service fall into three classes. Attack prevention and pre-emption acts before the attack: filtering forged addresses, closing services that can be used as reflectors, patching, and keeping enough capacity. Attack detection and filtering acts during the attack: recognising the attack traffic and discarding it as early as possible, as close to its source as possible. Attack source traceback and identification acts during and after the attack: finding where the traffic really came from, which is hard because source addresses are forged, and which supports the last resort, attack reaction, the response of the victim and its provider.

Prevention, before anything happens

MeasureWhat it stopsWhere it must be done
Ingress filtering (RFC 2827, BCP 38; RFC 3704 for multihomed networks)packets leaving with a forged source addressat every network's own edge
Not running open reflectors (RFC 5358)your servers being used to attack otherson every public server
Patching and hardeningmachines becoming botseverywhere
Capacity and spare pathssmall floods becoming outagesat the provider
Turning off services nobody useswhole classes of amplificationon every host

The awkward property of prevention is that it helps somebody else. A college that filters forged addresses protects strangers from its own machines. That is why these are published as Best Current Practices and argued for as a shared duty.

The SYN flood is prevented rather than filtered. RFC 4987's answers, SYN cookies in particular, remove the resource the attack aims at: if the server keeps no state until the handshake finishes, there is no table to fill.

Detection, while it runs

A flood is usually obvious in volume but not in kind, and the useful questions are: how quickly is the traffic rising, how many distinct sources are there, how are the sources distributed, and are the requests ones a normal client would make? The hard case is the flash crowd, a sudden rush of genuine users when results are published or a form opens. Telling the two apart is the base-rate problem of the intrusion detection chapters once more: a filter with a small error rate applied to a large volume of genuine traffic discards a great deal of it.

Practical detection for a college is unglamorous: alarms on link utilisation and on the rate of new connections, a record of what normal looks like on a normal day, and a telephone number for the provider that somebody has actually tested.

munotes.in615

DDoS Countermeasures

Reaction, once it is noticed

ResponseWhat it doesWho it helps
A firewall or rate limiter at your own endprotects the server's processor and tablesyou, but only if the link is not already full
Filtering upstream by the providerremoves attack traffic before your linkyou
Destination-based blackholing (RFC 3882, RFC 5635)the provider discards everything addressed to youeveryone else, at your total expense
Source-based blackholing (RFC 5635)discards traffic from named sources, wherever it is goingyou, and it also cuts those sources off from everything
Scrubbingall your traffic is diverted through a centre with capacity, cleaned, and returnedyou, at a price
Moving the servicenew addresses, or a content network in frontyou, if the attacker does not follow

Blackholing deserves the plain sentence RFC 5635 gives it. Destination-based filtering "injects a discard route ... All packets towards that destination, attack traffic AND legitimate traffic, are then dropped", so that "the impact of the attack on the target is complete". It is used because it protects the provider's other customers. For the victim it is the attacker's own goal, achieved efficiently.

The run: what each answer does to the good traffic

The listing takes a college with a 1 Gbit/s connection, 200 Mbit/s of genuine traffic and a 5 Gbit/s flood, and computes how much of the genuine traffic arrives under each response. A congested link cannot tell which packet matters, so each kind gets its share of the room.

# What each answer to a flood actually does to the traffic that matters: the good traffic.
# A link carries what it carries; the question is who gets the room. Nothing is sent anywhere.

LINK = 1_000_000_000          # the college's connection to its provider, 1 Gbit/s
GOOD = 200_000_000            # genuine traffic wanting to arrive, 200 Mbit/s
ATTACK = 5_000_000_000        # the flood aimed at it, 5 Gbit/s

def through(link, good, attack):
    """A congested link has no idea which packet matters: each kind gets its share."""
    offered = good + attack
    if offered <= link:
        return good, attack
    return link * good / offered, link * attack / offered

def report(name, good_in, attack_in, link=LINK, note=''):
    good_out, attack_out = through(link, good_in, attack_in)
    print('   %-31s %7.1f %8.1f %7.1f%%  %s'
          % (name, good_out / 1e6, attack_out / 1e6, 100 * good_out / GOOD, note))

print('A 1 Gbit/s LINK, 200 Mbit/s OF GENUINE TRAFFIC, A 5 Gbit/s FLOOD')
print('   %-31s %7s %8s %8s' % ('what is done', 'good', 'attack', 'of the'))
print('   %-31s %7s %8s %8s' % ('', 'Mbit/s', 'Mbit/s', 'good'))
report('nothing', GOOD, ATTACK)
report('a firewall at our own end', GOOD, ATTACK, note='drops it AFTER the link')
report('the provider blackholes us', 0, 0, note='we go offline')

# filtering upstream by source: p of the attack recognised, q of the good wrongly dropped
for p, q in ((0.90, 0.02), (0.99, 0.02), (0.99, 0.20)):
    report('provider filters %d%%, %d%% false' % (p * 100, q * 100),
           GOOD * (1 - q), ATTACK * (1 - p))

# scrubbing: everything is diverted through a centre with capacity to spare
for p, q in ((0.99, 0.02), (0.999, 0.01)):
    good_out = GOOD * (1 - q)
    attack_out = ATTACK * (1 - p)
    report('scrubbing centre, %.1f%% removed' % (p * 100), good_out, attack_out,
           link=10 * LINK, note='diverted and returned')

print()
print('READ THE SECOND LINE AGAIN: our own firewall changes nothing for the link,')
print('because the packets have already crossed it. Only the provider, upstream,')
print('can give the room back. Blackholing gives it back to everyone except us.')

# ---- and the attacks that do NOT fill the link ------------------------------------------------
print()
print('THE OTHER KIND: WHEN THE LINK IS NOT THE PROBLEM')
BACKLOG, TIMEOUT = 128, 120
link_packets = LINK / (54 * 8)
print('   a backlog of %d held %d s is kept full by %.1f packets a second'
      % (BACKLOG, TIMEOUT, BACKLOG / TIMEOUT))
print('   the link carries about %s packets a second, so the flood is %.7f of it'
      % (format(round(link_packets), ','), (BACKLOG / TIMEOUT) / link_packets))
print('   buying a bigger link does nothing, because the link was never the problem')
print('   with SYN cookies the server keeps no state until the handshake finishes,')
print('   so there is no table left to fill')
munotes.in616

DDoS Countermeasures

A 1 Gbit/s LINK, 200 Mbit/s OF GENUINE TRAFFIC, A 5 Gbit/s FLOOD
   what is done                       good   attack   of the
                                    Mbit/s   Mbit/s     good
   nothing                            38.5    961.5    19.2%
   a firewall at our own end          38.5    961.5    19.2%  drops it AFTER the link
   the provider blackholes us          0.0      0.0     0.0%  we go offline
   provider filters 90%, 2% false    196.0    500.0    98.0%
   provider filters 99%, 2% false    196.0     50.0    98.0%
   provider filters 99%, 20% false   160.0     50.0    80.0%
   scrubbing centre, 99.0% removed   196.0     50.0    98.0%  diverted and returned
   scrubbing centre, 99.9% removed   198.0      5.0    99.0%  diverted and returned

READ THE SECOND LINE AGAIN: our own firewall changes nothing for the link,
because the packets have already crossed it. Only the provider, upstream,
can give the room back. Blackholing gives it back to everyone except us.

THE OTHER KIND: WHEN THE LINK IS NOT THE PROBLEM
   a backlog of 128 held 120 s is kept full by 1.1 packets a second
   the link carries about 2,314,815 packets a second, so the flood is 0.0000005 of it
   buying a bigger link does nothing, because the link was never the problem
   with SYN cookies the server keeps no state until the handshake finishes,
   so there is no table left to fill
munotes.in617

DDoS Countermeasures

What the run establishes, in order.

With nothing done, four fifths of the genuine traffic is lost. The link carries 1 Gbit/s of the 5.2 Gbit/s offered, so only about 38 Mbit/s of the 200 Mbit/s wanted gets through: 19 per cent.

A firewall at your own end changes nothing about that. It reads the same 19 per cent, because the packets it drops have already crossed the link and used the room. This is the single most important practical fact in the chapter: against a link-filling attack, local equipment cannot help, however good it is. It protects the server behind it, not the road to it.

Blackholing is a complete outage that you asked for. The table shows 0.0 Mbit/s of genuine traffic, which is what RFC 5635 warns.

Filtering upstream is what actually works, and its false alarms are the whole design question. At 90 per cent of the attack removed the link is no longer full, and 98 per cent of genuine traffic arrives; pushing the attack removal from 90 to 99 per cent changes nothing for the good traffic, because the link already had room. But raising the false-alarm rate from 2 per cent to 20 per cent costs 18 points of genuine traffic. Beyond the point where the link stops being full, accuracy about genuine traffic matters more than accuracy about attack traffic.

The resource attacks are a different problem entirely. A SYN flood that keeps a 128-entry backlog full needs about one packet a second, against a link that carries some 2.3 million; buying more bandwidth is spending money on the wrong thing, and SYN cookies cost nothing.

What a small site can actually do

In order, and before anything happens:

  1. Know your provider's process. Who to call, what they can filter, whether they offer blackholing or scrubbing, and how long it takes. This is the only step that helps against a link-filling flood, and it cannot be arranged during one.
  2. Put something large in front, if the service matters: a content delivery network or hosted front end absorbs floods as part of its ordinary work.
  3. Turn on the cheap server defences: SYN cookies, connection limits per address, timeouts, and rate limits on expensive pages such as search and login.
  4. Do not be an amplifier. Close open DNS resolvers, NTP monitor commands, SNMP and anything else listening on UDP that need not be public.
  5. Apply ingress filtering on your own edge so that your machines cannot forge addresses.
  6. Record what normal looks like, so that an alarm means something.
  7. Write the plan down and name who decides, including who may accept a blackhole.
  8. Report. In India CERT-In's 2022 directions list denial of service and distributed denial of service among the incidents that must be reported within six hours.
munotes.in618

DDoS Countermeasures

Distinctions that carry marks

PreventionDetection and filteringTraceback
Whenbeforeduringduring and after
Examplesingress filtering, closing reflectors, patching, SYN cookiesanomaly detection, upstream filters, scrubbinglogging, provider cooperation, marking schemes
Limitneeds everyone to do iterrors cost genuine trafficforged addresses, many hops, many owners
BlackholingScrubbing
Doesdiscards everything for the addressseparates good from bad and returns the good
Victim's servicecompletely downmostly up
Protectsthe provider and its other customersthe victim
Costnone, and it is quicka paid service, and a diversion of traffic

What beginners get wrong here

Recommending a firewall against a bandwidth flood. It sits behind the link the attack has already filled.

Thinking a bigger link solves it. It raises the attacker's bill a little; against an amplified attack their bill is divided by hundreds.

Calling blackholing a defence without saying what it costs. It completes the outage for the target on purpose.

Optimising the wrong error. Once the attack is below the link's capacity, what matters is how little genuine traffic the filter discards.

Forgetting the other kind of attack. SYN floods and request floods need almost no bandwidth, and are answered by SYN cookies, limits and timeouts.

Quick revision

  • Three classes: prevention and pre-emption, detection and filtering, traceback and identification, with reaction as the response.
  • Prevention: BCP 38 ingress filtering, closing reflectors (RFC 5358), patching, capacity, SYN cookies.
  • Filtering works only upstream: a local firewall cannot recover a link that is already full.
  • Blackholing (RFC 3882, RFC 5635): the provider discards all traffic to the address; "the impact of the attack on the target is complete".
  • Scrubbing: divert, clean, return.
  • Run: nothing 19% of good traffic; local firewall 19%; blackhole 0%; upstream filter at 90% with 2% false alarms 98%; the same filter with 20% false alarms 80%.
  • A small site: know the provider's process, get something large in front, turn on server defences, do not be an amplifier, filter your own egress, know normal, write the plan, report within six hours.

Test yourself

1. Classify the countermeasures against distributed denial of service. They fall into attack prevention and pre-emption, before the attack, which includes ingress filtering of forged source addresses, closing services that can act as reflectors, patching machines so they do not become bots, removing the resources attacks target such as by using SYN cookies, and providing capacity; attack detection and filtering, during the attack, which means recognising attack traffic and discarding it as early and as near its source as possible; and attack source traceback and identification, during and after, which attempts to find where traffic really came from despite forged addresses. The victim's own response, from rate limiting to blackholing or scrubbing, is the reaction these support.

munotes.in619

DDoS Countermeasures

2. Why can a firewall at the victim's premises not defend against a bandwidth-filling flood? Because the attack consumes the access link before it reaches the firewall. A congested link carries only its capacity and has no way to prefer genuine packets, so genuine traffic is already lost in proportion; dropping the attack packets after they have crossed the link recovers nothing. In the worked example, a 5 Gbit/s flood against a 1 Gbit/s link leaves about 19 per cent of the genuine traffic arriving, whether or not there is a firewall. Only action upstream, by the provider, can return the room.

3. What is remote triggered black hole filtering, and what does it cost? It is a technique in which the victim asks its provider, usually by a routing announcement, to install a discard route for the attacked address, so that routers in the provider's network drop all packets towards it. It is fast and cheap and protects the provider's other customers and the rest of the network from the flood. Its cost falls entirely on the victim: as RFC 5635 states, attack traffic and legitimate traffic alike are dropped, taking the target completely offline, which is what the attacker wanted.

4. Why is a false alarm rate more important than a detection rate once filtering is in place? Because as soon as enough of the attack is removed for the link to have spare capacity, removing more of it gains nothing, while every percentage point of genuine traffic wrongly discarded is a direct loss of service. In the worked example, raising attack removal from 90 to 99 per cent left the genuine traffic unchanged at 98 per cent, while raising the false alarm rate from 2 to 20 per cent cut it to 80 per cent: the filter would then be doing part of the attacker's work.

5. What should a college do in advance to be ready for a denial of service attack? Agree with its provider, before anything happens, who to contact and what filtering, blackholing or scrubbing is available and how quickly; put a content delivery network or hosted front end in front of services that matter; enable the inexpensive server defences, SYN cookies, per-address connection limits, timeouts and rate limits on expensive pages; close any service that could make it an amplifier, such as an open DNS resolver; apply ingress filtering at its own edge so its machines cannot forge addresses; record what normal traffic looks like so that alarms are meaningful; write down who decides what, including who may accept being blackholed; and be ready to report the incident to CERT-In within six hours as the 2022 directions require.

Contents This chapter on its own page

munotes.in620

Chapter Ninety-Three

Firewall Design Principles

Syllabus topic Module 2, "Firewalls: Firewall Design Principles"

In one line

A firewall is a single controlled doorway between a network you trust and one you do not, and it works only if every packet must go through it, only what the policy allows is let past, and the firewall itself cannot be broken into.

In the words an answer should use: a firewall is inserted between the premises network and the Internet to establish a controlled link and a security perimeter, forming a single choke point at which security and auditing can be imposed. It has three design goals: first, all traffic from inside to outside, and from outside to inside, must pass through the firewall; second, only authorised traffic, as defined by the local security policy, will be allowed to pass; and third, the firewall itself is immune to penetration, which implies a hardened system with a secured operating system.

The four techniques of control

This is the textbook's list of the ways a firewall controls access, and every one of them appears in a real rule set.

TechniqueControlsExample
Service controlwhich services may be reached, inbound or outbound, by protocol, port number or by proxying the service itselfthe web server may be reached from outside; the database may not
Direction controlwhich way a service may be startedstaff may open connections to the Internet; the Internet may not open connections to staff machines
User controlwhich users may use a serviceonly administrators may reach the management network, verified by who the user is
Behaviour controlhow a service may be usedan email filter that removes attachments, a proxy that blocks certain pages

What a firewall can do

  • It defines a single choke point: one place to keep unauthorised users out, to forbid vulnerable services, and to watch.
  • It is a good place for monitoring: logs and alarms on one device see everything that crosses.
  • It is a convenient place for functions that are not strictly security, such as network address translation.
  • It can be the endpoint of IPsec tunnels, and so carry a virtual private network.

What a firewall cannot do

  • It cannot protect against what does not go through it. A mobile hotspot on a laboratory machine, a forgotten modem, or a laptop carried in and plugged in, all bypass the perimeter.
  • It cannot protect against insiders. A clerk with a legitimate login who emails the marks list has broken no firewall rule.
  • It cannot stop what it cannot read. Most traffic is encrypted; a firewall sees that a machine is talking to somewhere over HTTPS, not what it is saying.
  • It cannot stop what it is asked to let through. Malware downloaded through the web service the policy permits arrives through an open door.
  • It cannot defend against everything transferred by other means. A file on a USB stick never touches the network.
munotes.in621

Firewall Design Principles

Deny by default. NIST states the rule plainly: "deny by default is a more secure approach than permitting all traffic that is not explicitly forbidden". Everything else in a firewall's design follows from that sentence, because a rule set that starts by allowing everything can never be made complete.

Ingress and egress

NIST names both directions. Ingress filtering is filtering traffic coming in. Egress filtering is filtering traffic going out, and it does two jobs: it stops people inside using services they should not, and it stops the organisation attacking others, since "Organizations should only permit outbound traffic that uses the source IP addresses in use by the organization", which is the ingress filtering of the denial-of-service chapters seen from the other side.

The run: what actually reaches the doorway

The listing applies the four controls to six requests, then asks of ten ordinary events in a college whether they cross the perimeter at all and whether the firewall can tell what they are, and finally counts the ways into the network that the firewall does not stand on.

# What a firewall controls, and what passes it untouched. Twelve things that happen in a college,
# each asked two questions: does it cross the firewall, and can the firewall tell what it is?

# ---- 1. the four kinds of control, each on one request ----------------------------------------
POLICY = {
    'services allowed in': {('web', 443), ('mail', 25)},
    'services allowed out': {('web', 443), ('web', 80), ('dns', 53)},
    'may reach the staff network': {'staff', 'admin'},
    'upload limit out, MB': 20,
}

REQUESTS = [
    ('a visitor opens the college website', 'in', ('web', 443), 'guest', 2),
    ('a visitor tries the database port', 'in', ('sql', 3306), 'guest', 1),
    ('a student browses a news site', 'out', ('web', 443), 'student', 3),
    ('a student uploads 900 MB somewhere', 'out', ('web', 443), 'student', 900),
    ('a student opens the staff file server', 'out', ('smb', 445), 'student', 5),
    ('a clerk opens the staff file server', 'out', ('smb', 445), 'staff', 5),
]

def decide(direction, service, who, megabytes):
    """The four controls the textbooks name, in the order they are usually applied."""
    if direction == 'in' and service not in POLICY['services allowed in']:
        return 'DENY', 'service control: not offered outside'
    if direction == 'out' and service[0] == 'smb':
        if who not in POLICY['may reach the staff network']:
            return 'DENY', 'user control: not this person'
        return 'ALLOW', 'user control: this person may'
    if direction == 'out' and service not in POLICY['services allowed out']:
        return 'DENY', 'service control: not allowed out'
    if megabytes > POLICY['upload limit out, MB']:
        return 'DENY', 'behaviour control: too much at once'
    return 'ALLOW', 'direction control: this way round'

print('1. THE FOUR CONTROLS, ONE REQUEST EACH')
for what, direction, service, who, megabytes in REQUESTS:
    verdict, why = decide(direction, service, who, megabytes)
    print('   %-39s %-6s %s' % (what, verdict, why))

# ---- 2. what never reaches it, and what it cannot read ----------------------------------------
# (description, does it cross the perimeter, can the firewall tell what it is)
EVENTS = [
    ('a worm arrives on a visiting laptop', False, False),
    ('a student copies files to a USB stick', False, False),
    ('a clerk emails the marks list to a friend', True, False),
    ('a staff phone shares mobile data to a PC', False, False),
    ('an outsider scans the college web server', True, True),
    ('malware on a PC calls home over HTTPS', True, False),
    ('someone downloads a Trojan from a web page', True, False),
    ('a student guesses an internal password', False, False),
    ('an outsider floods the college link', True, True),
    ('a lecturer plugs in an infected drive', False, False),
]
print()
print('2. WHAT A PERIMETER FIREWALL CAN DO ANYTHING ABOUT')
print('   %-44s %-9s %s' % ('what happens', 'crosses?', 'can it read it?'))
for what, crosses, legible in EVENTS:
    print('   %-44s %-9s %s' % (what, 'yes' if crosses else 'no',
                                'yes' if legible else 'no'))
crossing = sum(c for _, c, _ in EVENTS)
actionable = sum(c and l for _, c, l in EVENTS)
print('   %d of %d cross it; it can act on %d' % (crossing, len(EVENTS), actionable))

# ---- 3. a firewall is only a choke point if it is the ONLY way in ------------------------------
print()
print('3. THE CHOKE POINT')
PATHS = [('the leased line, through the firewall', True),
         ('the library wireless, through the firewall', True),
         ('a staff mobile hotspot on a laboratory PC', False),
         ('a forgotten modem on the accounts machine', False)]
for name, guarded in PATHS:
    print('   %-43s %s' % (name, 'guarded' if guarded else 'NOT GUARDED'))
guarded = sum(g for _, g in PATHS)
print('   %d of %d ways in are guarded: the firewall is a choke point only for those'
      % (guarded, len(PATHS)))
print('   one forgotten path is enough, which is why the first design goal is that ALL')
print('   traffic must pass through the firewall')
munotes.in622

Firewall Design Principles

1. THE FOUR CONTROLS, ONE REQUEST EACH
   a visitor opens the college website     ALLOW  direction control: this way round
   a visitor tries the database port       DENY   service control: not offered outside
   a student browses a news site           ALLOW  direction control: this way round
   a student uploads 900 MB somewhere      DENY   behaviour control: too much at once
   a student opens the staff file server   DENY   user control: not this person
   a clerk opens the staff file server     ALLOW  user control: this person may

2. WHAT A PERIMETER FIREWALL CAN DO ANYTHING ABOUT
   what happens                                 crosses?  can it read it?
   a worm arrives on a visiting laptop          no        no
   a student copies files to a USB stick        no        no
   a clerk emails the marks list to a friend    yes       no
   a staff phone shares mobile data to a PC     no        no
   an outsider scans the college web server     yes       yes
   malware on a PC calls home over HTTPS        yes       no
   someone downloads a Trojan from a web page   yes       no
   a student guesses an internal password       no        no
   an outsider floods the college link          yes       yes
   a lecturer plugs in an infected drive        no        no
   5 of 10 cross it; it can act on 2

3. THE CHOKE POINT
   the leased line, through the firewall       guarded
   the library wireless, through the firewall  guarded
   a staff mobile hotspot on a laboratory PC   NOT GUARDED
   a forgotten modem on the accounts machine   NOT GUARDED
   2 of 4 ways in are guarded: the firewall is a choke point only for those
   one forgotten path is enough, which is why the first design goal is that ALL
   traffic must pass through the firewall
munotes.in623

Firewall Design Principles

What the run establishes, in order.

The four controls are four different questions. The same request may be refused because the service is not offered, because it is going the wrong way, because of who is asking, or because of how much is being sent. A rule set is where those four questions are written down.

Half of the trouble never comes near it. Five of the ten events cross the perimeter, and of those the firewall can tell what only two of them are: a scan and a flood. The marks list leaving by email, the malware calling home over HTTPS and the Trojan coming down inside a permitted web session all cross the firewall unreadable. This is not a criticism of firewalls; it is the reason the syllabus also contains intrusion detection, antivirus and access control.

A firewall is a choke point only where it stands. Two of the four ways into the example network do not pass through it, and either one is enough to make the perimeter a fiction. This is the first design goal restated: all traffic must pass through, and that is an administrative fact about the building and the staff, not a setting on a device.

Distinctions that carry marks

A firewallAn intrusion detection system
Doespermits or refuses trafficreports what it thinks it sees
Sitsin the pathin the path or beside it
Failure modeblocks legitimate work, or allows an attacka missed attack, or a false alarm
munotes.in624

Firewall Design Principles

Ingress filteringEgress filtering
Directiontraffic coming intraffic going out
Protectsus from outsideoutside from us, and us from ourselves
Typical ruleonly these services may be reachedonly our own source addresses may leave

What beginners get wrong here

Saying a firewall makes a network secure. It controls one doorway; insiders, encrypted content, permitted services and any other path go round it.

Forgetting the third design goal. A firewall that can itself be broken into is worse than none, because everything is trusted to it.

Starting a rule set with allow. Deny by default is the only order that can be made complete.

Confusing user control with service control. Service control asks what may be reached; user control asks who is asking.

Quick revision

  • Firewall: a controlled link and security perimeter, a single choke point.
  • Three design goals: all traffic must pass through it; only authorised traffic passes; the firewall itself is immune to penetration.
  • Four controls: service (what), direction (which way), user (who), behaviour (how).
  • Can: choke point, monitoring, address translation, VPN endpoint.
  • Cannot: what goes round it, insiders, encrypted content, what the policy permits, files on removable media.
  • Deny by default; ingress and egress filtering, the second including only our own source addresses leaving.
  • Run: of ten college events, 5 of 10 cross the firewall and it can act on 2.

Test yourself

1. What is a firewall, and what are its design goals? A firewall is a device or system placed between a trusted internal network and an untrusted external one, forming a controlled link and a security perimeter, so that there is a single choke point at which security policy and auditing can be applied. Its three design goals are that all traffic in both directions must pass through it; that only traffic authorised by the local security policy is allowed to pass; and that the firewall itself is immune to penetration, which requires a hardened, minimal and well-maintained system.

2. Describe the four techniques by which a firewall controls access. Service control decides which services may be accessed, inbound or outbound, by filtering on protocol and port number or by proxying the service. Direction control decides in which direction a particular service may be initiated, for example allowing inside machines to open connections outwards but not the reverse. User control decides which users may use a service, which is straightforward for local users and requires an authentication mechanism for external ones. Behaviour control decides how a service may be used, such as filtering email attachments or restricting which pages a proxy will fetch.

3. List what a firewall can and cannot protect against. It can impose a single point of control and auditing, keep unauthorised traffic out, forbid vulnerable services, monitor security-relevant events, and serve as a platform for network address translation and for VPN endpoints. It cannot protect against traffic that does not pass through it, such as a mobile hotspot or an unauthorised modem; against insiders acting within their permissions; against content it cannot read, which today is most traffic, as it is encrypted; against malicious content carried inside services the policy permits; or against files brought in on removable media.

munotes.in625

Firewall Design Principles

4. What does deny by default mean, and why is it the right order? It means the rule set ends with a rule refusing everything not explicitly permitted, so that only traffic somebody has deliberately allowed can pass. NIST states that it is more secure than permitting everything not explicitly forbidden, because a policy of listing what to forbid can never be complete: a service nobody thought of, or one added later, is permitted by omission, whereas under deny by default an omission blocks something and is noticed and corrected.

5. Distinguish ingress filtering from egress filtering. Ingress filtering examines traffic entering the network and is the familiar protective function, allowing only permitted services to be reached from outside. Egress filtering examines traffic leaving, and serves two purposes: enforcing internal policy, such as forbidding the use of outside file transfer services, and preventing the organisation's machines from attacking others, in particular by permitting outbound traffic only if it carries a source address the organisation actually owns, which prevents forged addresses leaving.

Contents This chapter on its own page

munotes.in626

Chapter Ninety-Four

Types of Firewalls

Syllabus topic Module 2, "Firewalls: Types of Firewalls"

In one line

The four kinds differ in how much of the traffic they look at: a packet filter sees one packet's addresses, ports and flags; stateful inspection remembers the connections; a circuit-level gateway relays whole connections without reading them; and an application-level gateway understands the protocol and reads the request itself.

In the words an answer should use: a packet-filtering router applies a set of rules to each incoming and outgoing IP packet and then forwards or discards it, filtering on source and destination address, source and destination port, protocol and the interface. A stateful inspection firewall additionally keeps a directory of outbound TCP connections and allows incoming traffic only where it belongs to one of them. An application-level gateway, or proxy server, relays application traffic: the user contacts the gateway, which then contacts the remote host on the user's behalf, and it can examine the contents at the application layer. A circuit-level gateway sets up two TCP connections, one to an inside user and one to an outside host, and relays segments between them without examining their contents.

Packet filtering

A packet filter applies a rule set to each packet on its own. NIST lists what a basic filter can look at: the source address, the destination address, the protocol, "some characteristics of the transport layer communications sessions, such as session source and destination ports", and "the interface being traversed by the packet, and its direction".

Its weakness is that it has no memory. It cannot associate a reply with the request that caused it, so to let ordinary web browsing work it must either open all high-numbered ports inbound, or use the approximation that a packet with the ACK flag set must belong to an existing connection. The run shows what that approximation costs.

Attacks and countermeasures.

AttackWhat it doesCountermeasure
Address spoofingsends packets from outside claiming an inside source addressdiscard packets arriving on the outside interface with an inside source address
Source routingthe packet carries the route it wants to take, avoiding the filter's assumptionsdiscard packets that use the source routing option
Tiny fragmentssplits the header across fragments so that the port numbers are not in the first onediscard fragments too small to hold the header, or reassemble before filtering

Stateful inspection

Stateful inspection "improves on the functions of packet filters by tracking the state of connections and blocking packets that deviate from the expected state". It keeps a state table whose entries typically hold "source IP address, destination IP address, port numbers, and connection state information", and it tightens the rule set by making inbound traffic conditional on an outbound connection that actually exists.

What it costs. The table is memory, and it is finite, which is the resource a SYN flood attacks. A stateful firewall under a connection flood can fail even when the link is not full.

munotes.in627

Types of Firewalls

Application-level gateway

An application-level gateway, also called a proxy server, works at the level of the protocol itself. The user connects to the gateway and names the service wanted; the gateway, if the policy allows, connects to the remote host and relays between the two. NIST: the proxy agent "never allows a direct connection", so that "Each successful connection attempt actually results in the creation of two separate connections", and "Because external hosts only communicate with the proxy agent, internal IP addresses are not visible to the outside world".

Its advantage is that it can read the request. It can allow a fetch and refuse an upload, allow one site and refuse another, remove an attachment, or require the user to authenticate. A modern firewall that does this is what NIST calls an application firewall, able to tell "if instant messaging is being used over port 80".

Its cost is work. A separate proxy is needed for every application, and the gateway handles every byte, so it is slower and is itself a large piece of software to keep secure.

Circuit-level gateway

A circuit-level gateway sets up two TCP connections and, once they are open, "relays TCP segments from one connection to the other without examining the contents". The security function is deciding which connections are allowed, not what travels through them.

The real example is SOCKS, which RFC 1928 describes as "a general framework for these protocols to transparently and securely traverse a firewall", sitting "between the application layer and the transport layer". Because it does not understand the applications, one relay serves them all; because it does not read them, it cannot filter what they carry.

A typical use is outbound connections from users who have authenticated, where the organisation trusts its own people but wants no direct route between inside and outside.

Bastion host

A bastion host is the system a firewall's application-level or circuit-level gateway runs on: a machine identified by the administrator as a critical strong point, hardened to the extent that everything unnecessary is removed. Typically it runs a secure operating system, only the services that are essential, each proxy running with least privilege in its own restricted directory, and it keeps detailed logs. It is the machine an attacker must take, so it is the machine that is most carefully built and most closely watched.

The run: the same eight packets, four firewalls

The listing offers the same eight packets to a stateless filter, a stateful firewall, an application gateway and a circuit-level relay. Each decides with only what it can see.

munotes.in628

Types of Firewalls

# The same eight packets offered to four kinds of firewall. Each one decides with only what it
# is able to see, which is the whole difference between them. Nothing is sent anywhere.

INSIDE = '10.21.'
ALLOWED_SITES = {'library.example.edu', 'mu.ac.in'}

# direction, source, source port, destination, destination port, TCP flags, what it carries
PACKETS = [
    ('out', '10.21.4.9', 51_001, '203.0.113.5', 80, 'SYN', ''),
    ('in', '203.0.113.5', 80, '10.21.4.9', 51_001, 'SYN ACK', ''),
    ('out', '10.21.4.9', 51_001, '203.0.113.5', 80, 'ACK', 'GET / library.example.edu'),
    ('in', '198.51.100.7', 80, '10.21.4.9', 51_002, 'ACK', 'nobody asked for this'),
    ('in', '198.51.100.7', 4444, '10.21.9.2', 3389, 'SYN', ''),
    ('out', '10.21.7.1', 51_010, '203.0.113.9', 80, 'SYN', ''),
    ('out', '10.21.7.1', 51_010, '203.0.113.9', 80, 'ACK', 'GET / betting.example.com'),
    ('out', '10.21.7.1', 51_011, '203.0.113.9', 80, 'ACK', 'PUT /upload marks.xlsx'),
]

# ---- 1. a stateless packet filter: addresses, ports and flags, one packet at a time -----------
def packet_filter(p):
    direction, src, sport, dst, dport, flags, _ = p
    if direction == 'out':
        return dport in (80, 443, 53), 'outbound to a permitted port'
    if dport in (80, 443) and 'SYN' in flags and 'ACK' not in flags:
        return False, 'nobody may start a connection to us'
    # the classic approximation: "if it is not a new connection, it must be a reply"
    if 'ACK' in flags and sport in (80, 443):
        return True, 'looks like a reply, because ACK is set'
    return False, 'no rule permits it'

# ---- 2. stateful inspection: the same, but it remembers what we started -----------------------
class Stateful:
    def __init__(self):
        self.table = set()

    def decide(self, p):
        direction, src, sport, dst, dport, flags, _ = p
        if direction == 'out':
            if dport not in (80, 443, 53):
                return False, 'outbound port not permitted'
            self.table.add((src, sport, dst, dport))
            return True, 'allowed out, and written into the state table'
        if (dst, dport, src, sport) in self.table:
            return True, 'matches a connection we started'
        return False, 'no entry in the state table'

# ---- 3. an application-level gateway: it reads the request the protocol carries ----------------
def application_gateway(p):
    direction, src, sport, dst, dport, flags, payload = p
    if direction == 'in':
        return False, 'the outside talks to the proxy, never to a host'
    if not payload:
        return True, 'connection to the proxy itself'
    method, _, site = payload.partition(' ')[0], None, payload.split()[-1]
    if method not in ('GET', 'HEAD'):
        return False, 'method %s is not permitted outward' % method
    if site not in ALLOWED_SITES:
        return False, 'site %s is not on the list' % site
    return True, 'fetched by the proxy on the user\'s behalf'

# ---- 4. a circuit-level gateway: it relays, after checking who is asking -----------------------
AUTHENTICATED = {'10.21.4.9'}

def circuit_gateway(p):
    direction, src, sport, dst, dport, flags, payload = p
    if direction == 'in':
        return False, 'relays are set up from the inside only'
    if src not in AUTHENTICATED:
        return False, 'this client has not authenticated to the relay'
    return True, 'relayed without looking at what it carries'

stateful = Stateful()
print('EIGHT PACKETS, FOUR FIREWALLS')
print('   %-3s %-35s %-8s %-9s %-8s %s'
      % ('', 'packet', 'filter', 'stateful', 'gateway', 'circuit'))
counts = {'filter': 0, 'stateful': 0, 'gateway': 0, 'circuit': 0}
for n, p in enumerate(PACKETS, 1):
    direction, src, sport, dst, dport, flags, payload = p
    verdicts = {'filter': packet_filter(p)[0], 'stateful': stateful.decide(p)[0],
                'gateway': application_gateway(p)[0], 'circuit': circuit_gateway(p)[0]}
    for key, ok in verdicts.items():
        counts[key] += ok
    short = {'SYN': 'SYN', 'SYN ACK': 'S-A', 'ACK': 'ACK'}[flags]
    label = '%s %s to port %d, %s' % (direction, src, dport, short)
    print('   %-3s %-35s %-8s %-9s %-8s %s'
          % (n, label[:35], *['pass' if verdicts[k] else 'DROP'
                              for k in ('filter', 'stateful', 'gateway', 'circuit')]))
print('   %-3s %-35s %-8s %-9s %-8s %s'
      % ('', 'let through, of 8', *[counts[k] for k in ('filter', 'stateful', 'gateway',
                                                        'circuit')]))

# ---- where they differ, in words ---------------------------------------------------------------
print()
print('WHERE THEY DIFFER')
for n in (4, 7, 8):
    p = PACKETS[n - 1]
    print('   packet %d: %s' % (n, p[6] or 'no payload'))
    print('      packet filter    %s' % packet_filter(p)[1])
    print('      stateful         %s' % Stateful().decide(p)[1])
    print('      application      %s' % application_gateway(p)[1])
munotes.in629

Types of Firewalls

EIGHT PACKETS, FOUR FIREWALLS
       packet                              filter   stateful  gateway  circuit
   1   out 10.21.4.9 to port 80, SYN       pass     pass      pass     pass
   2   in 203.0.113.5 to port 51001, S-A   pass     pass      DROP     DROP
   3   out 10.21.4.9 to port 80, ACK       pass     pass      pass     pass
   4   in 198.51.100.7 to port 51002, ACK  pass     DROP      DROP     DROP
   5   in 198.51.100.7 to port 3389, SYN   DROP     DROP      DROP     DROP
   6   out 10.21.7.1 to port 80, SYN       pass     pass      pass     DROP
   7   out 10.21.7.1 to port 80, ACK       pass     pass      DROP     DROP
   8   out 10.21.7.1 to port 80, ACK       pass     pass      DROP     DROP
       let through, of 8                   7        6         3        2

WHERE THEY DIFFER
   packet 4: nobody asked for this
      packet filter    looks like a reply, because ACK is set
      stateful         no entry in the state table
      application      the outside talks to the proxy, never to a host
   packet 7: GET / betting.example.com
      packet filter    outbound to a permitted port
      stateful         allowed out, and written into the state table
      application      site betting.example.com is not on the list
   packet 8: PUT /upload marks.xlsx
      packet filter    outbound to a permitted port
      stateful         allowed out, and written into the state table
      application      method PUT is not permitted outward

What the run establishes, in order.

The stateless filter lets in a packet that answers nothing. Packet 4 arrives from outside, from port 80, with the ACK flag set, addressed to a port nobody is using. The filter's rule "if ACK is set it must be a reply" passes it, because a single packet carries no proof that a connection exists. The stateful firewall consults its table, finds no such connection, and drops it. This one row is the reason stateful inspection exists.

munotes.in630

Types of Firewalls

Everything the stateless filter and the stateful firewall allow outward, they allow blindly. Packets 7 and 8 are ordinary web traffic by every test either of them can apply: permitted port, permitted direction, a connection that was properly opened. Only the gateway, which reads the request, sees that one asks for a site the policy forbids and the other is uploading a file outward.

The circuit-level relay decides about people, not content. It passes what the authenticated client sends and refuses the client that has not authenticated, without knowing or caring that one of the requests is an upload.

The counts are the summary of the chapter: of eight packets, the stateless filter allows seven, the stateful firewall six, the application gateway three, and the circuit relay two. Each step down that list is a step in how much is examined, and a step up in cost.

Distinctions that carry marks

Packet filterStateful inspectionCircuit-level gatewayApplication-level gateway
Layernetworknetwork and transporttransport (session)application
Looks atone packet: addresses, ports, flagspackets plus a table of connectionswhich connections may be set upthe request itself
Reads contentnononoyes
Hides inside addressesnonoyesyes
Speedfastestfastfastslowest
One for every applicationnononoyes
Typical weaknessno memory, spoofing, fragmentsthe state table is finitecannot see what it relayscost, complexity, one proxy per service

What beginners get wrong here

Saying a packet filter can allow "replies". It can only guess from flags; only a stateful firewall knows whether a connection exists.

Thinking an application gateway is just a faster filter. It is a different architecture: two connections, not one, and the inside host never speaks to the outside host.

Confusing circuit-level with application-level. Both relay; only the application-level gateway looks at what is being relayed.

Forgetting that a proxy hides addresses. NIST notes that outside hosts see only the proxy, so internal addresses never appear outside.

Treating the bastion host as a type of firewall. It is the hardened machine on which the gateway runs.

Quick revision

  • Packet filter: rules on one packet, addresses, ports, protocol, interface; stateless; fast; weak to spoofing, source routing, tiny fragments.
  • Stateful inspection: a state table of connections; inbound allowed only if it belongs to one; the table is finite.
  • Application-level gateway (proxy): two connections, reads the protocol, can allow a GET and refuse a PUT, hides inside addresses; slow; one proxy per service.
  • Circuit-level gateway: relays TCP segments "without examining the contents"; decides which connections may exist; SOCKS, RFC 1928.
  • Bastion host: the hardened machine the gateways run on; minimal services, least privilege, heavy logging.
  • Run: of 8 packets, filter 7, stateful 6, gateway 3, circuit 2.
munotes.in631

Types of Firewalls

Test yourself

1. Describe a packet-filtering router and the attacks it is subject to. It applies a set of rules to each IP packet individually and forwards or discards it, matching on source and destination addresses, source and destination ports, the protocol, and the interface and direction. Because it keeps no record of connections, it must approximate when allowing replies. Its attacks include IP address spoofing, in which an outsider sends packets carrying an inside source address, countered by discarding such packets arriving on the external interface; source routing attacks, in which the packet specifies its own route, countered by discarding packets that use the option; and tiny fragment attacks, in which the header is split so that port numbers fall outside the first fragment, countered by discarding over-small fragments or reassembling before filtering.

2. How does stateful inspection improve on packet filtering? It keeps a state table recording the connections that have been established, typically with source and destination addresses, port numbers and connection state. Instead of guessing from flags whether an inbound packet is a legitimate reply, it checks whether that packet belongs to a connection that was properly opened from inside, and drops it if not. This closes the classic weakness in which a packet with the ACK flag set is admitted although no connection exists. Its cost is that the table consumes memory and is finite, so it can be exhausted by a flood of connections.

3. Explain an application-level gateway, with its advantages and disadvantages. An application-level gateway, or proxy server, accepts a connection from the user, examines the request at the level of the application protocol, and, if the policy permits, makes its own connection to the remote host and relays between the two, so there is never a direct connection between inside and outside. Its advantages are that it can inspect and filter the content of the requests, allowing some operations and refusing others, that it can authenticate users, that it logs at the level of the application, and that external hosts see only the proxy so internal addresses stay hidden. Its disadvantages are the processing cost of handling every byte and the need for a separate proxy for each supported application.

munotes.in632

Types of Firewalls

4. What is a circuit-level gateway, and when is it used? It is a relay that sets up two TCP connections, one to the inside user and one to the outside host, and then copies segments between them without examining their contents; its security decision is which connections may be established. It is typically used for outbound traffic from users the organisation trusts, giving them access without any direct route between the internal and external networks; SOCKS is the standard example. It cannot filter what it relays, so it is often used together with application-level proxies for the services that need inspection.

5. What is a bastion host? It is the system on which a firewall's application-level or circuit-level gateway runs: a machine identified as a critical strong point in the network's security and hardened accordingly. It runs a secure, trimmed operating system with only essential services, each proxy runs with the least privilege it needs and in a restricted directory, no user accounts are kept on it where avoidable, and it keeps detailed logs. Because it is the machine an attacker must compromise to get through, it is built and watched more carefully than any other.

Contents This chapter on its own page

munotes.in633

Chapter Ninety-Five

Firewall Configurations, and a Rule Base Read Line by Line

Syllabus topic Module 2, "Firewalls: Firewall Configurations" and Practical 5, "Configure and test firewall rules"

In one line

The three classic arrangements differ in how many independent things an attacker must defeat: a screened host puts a bastion beside the internal network; a dual-homed bastion removes the physical path round it; and a screened subnet puts a whole network, with two routers, between inside and outside.

In the words an answer should use: there are three common firewall configurations. In the screened host firewall, single-homed bastion configuration, a packet-filtering router allows Internet traffic only to the bastion host, and only the bastion may send traffic out. In the screened host firewall, dual-homed bastion configuration, the bastion host has two network interfaces and the router's traffic must physically pass through it, so there is no direct path between the Internet and the internal network. In the screened subnet firewall configuration, two packet-filtering routers are used, one between the Internet and the bastion and one between the bastion and the internal network, creating an isolated subnetwork between them; this is the most secure of the three.

The three configurations

Three arrangements drawn one above the other. First, a screened host with a single-homed bastion: the Internet reaches a packet-filtering router, which connects to the internal network, and the bastion host hangs off that same internal network. Second, a screened host with a dual-homed bastion: the Internet reaches the router, the router reaches the bastion host, which has two cards, and only the bastion reaches the inside hosts, so there is no path round it. Third, a screened subnet: the Internet reaches an outer router, which connects to a middle network where the bastion and the public servers live, and an inner router separates that middle network from the inside hosts.

Figure 95.1 Each step adds something an attacker must defeat separately.

Screened host, single-homed bastion. The router's rules say that traffic from outside may go only to the bastion, and that traffic to outside may come only from the bastion. Two things must therefore be defeated, the router's rules and the bastion. Its weakness is that the bastion sits on the internal network, so if the router's rules are wrong, the inside hosts can be reached directly, and if the bastion is taken, it is already inside.

Screened host, dual-homed bastion. The bastion has two interfaces and the two networks are physically separate, so traffic cannot go round it even if the router is misconfigured. Internal machines that must be reachable from outside can be given a path through the bastion; the rest are unreachable by construction.

Screened subnet. Two routers and a network between them, on which the bastion and the public servers sit. It gives three levels to defeat, and two further properties that matter: the internal network is invisible to the Internet, because its addresses are never advertised outside; and the inside machines cannot reach the Internet directly, only through the inner router's rules. NIST calls this middle network a DMZ, and notes that "It is common to put public-facing servers, such as web and email servers, on the DMZ".

Reading a rule base

A rule base is a list of rules that a firewall reads from the top, applying the first one that matches and stopping. The last rule denies everything, so that anything nobody thought about is refused: NIST's "deny by default".

A rule usually names, in some order: a number, an action (allow or deny), a source, a destination, a protocol, a port, and a comment saying why. The comment is not decoration. A rule whose purpose nobody remembers is a rule nobody dares remove, and rule bases grow until nobody understands them.

munotes.in634

Firewall Configurations, and a Rule Base Read Line by Line

Two ordering mistakes account for most quiet failures.

  1. A broad allow above a specific deny. The deny is correct, is in the file, and never runs, because the packet has already matched the allow above it.
  2. A rule below the final deny-all. It is correct, and unreachable, because everything matched the deny.

Both look right when the file is read as a list of intentions, and both are invisible until somebody tests.

The run: the same policy, twice

The listing takes one policy, written as it usually is first written, and then reordered. Six test packets are put to both, each with the verdict intended. It also aims one sample packet at each rule to see which rule actually catches it, which is how a rule that can never fire shows itself.

# A rule base read the way a firewall reads it: first match wins, and the last rule denies.
# Two versions of the same policy, one with two ordering mistakes, both tested against the
# same packets. Nothing is sent anywhere; a "packet" is five fields in a tuple.
import ipaddress

ANY = 'any'

def matches(field, value):
    if field == ANY:
        return True
    if isinstance(value, int):
        return value in field if isinstance(field, (set, range)) else field == value
    return ipaddress.ip_address(value) in ipaddress.ip_network(field)

def evaluate(rules, packet):
    """Walk the rules in order and stop at the first that matches, as a firewall does."""
    src, dst, proto, port = packet
    for number, action, r_src, r_dst, r_proto, r_port, why in rules:
        if (matches(r_src, src) and matches(r_dst, dst)
                and (r_proto == ANY or r_proto == proto)
                and (r_port == ANY or matches(r_port, port))):
            return number, action, why
    return None, 'deny', 'nothing matched'

INSIDE, SERVERS, EXAM = '10.21.0.0/16', '10.21.50.0/24', '10.21.99.0/24'
WEB = {80, 443}

# as first written: it looks correct, and two of its rules can never fire
AS_WRITTEN = [
    (1, 'allow', ANY, SERVERS, 'tcp', WEB, 'the public may reach the web servers'),
    (2, 'allow', INSIDE, ANY, 'tcp', ANY, 'our people may go out'),
    (3, 'deny', EXAM, ANY, ANY, ANY, 'the examination network must never reach outside'),
    (4, 'deny', ANY, ANY, ANY, ANY, 'deny everything else'),
    (5, 'allow', ANY, SERVERS, 'tcp', {22}, 'administrators reach the servers by ssh'),
]

# the same policy, ordered so that each rule can do its work
CORRECTED = [
    (1, 'deny', EXAM, ANY, ANY, ANY, 'the examination network must never reach outside'),
    (2, 'allow', ANY, SERVERS, 'tcp', WEB, 'the public may reach the web servers'),
    (3, 'allow', '10.21.7.0/24', SERVERS, 'tcp', {22}, 'administrators reach the servers by ssh'),
    (4, 'allow', INSIDE, ANY, 'tcp', ANY, 'our people may go out'),
    (5, 'deny', ANY, ANY, ANY, ANY, 'deny everything else'),
]

TESTS = [
    ('a visitor reads the college website', ('198.51.100.4', '10.21.50.8', 'tcp', 443), 'allow'),
    ('a student browses the web', ('10.21.4.9', '203.0.113.5', 'tcp', 443), 'allow'),
    ('an examination machine reaches out', ('10.21.99.5', '203.0.113.5', 'tcp', 443), 'deny'),
    ('an administrator uses ssh', ('10.21.7.2', '10.21.50.8', 'tcp', 22), 'allow'),
    ('a stranger tries ssh on a server', ('198.51.100.4', '10.21.50.8', 'tcp', 22), 'deny'),
    ('a stranger tries the database', ('198.51.100.4', '10.21.50.8', 'tcp', 3306), 'deny'),
]

for title, rules in (('AS FIRST WRITTEN', AS_WRITTEN), ('AFTER REORDERING', CORRECTED)):
    print('%s' % title)
    wrong = 0
    for what, packet, expected in TESTS:
        number, action, why = evaluate(rules, packet)
        ok = action == expected
        wrong += not ok
        print('   %-38s rule %-4s %-6s %s'
              % (what, number if number else '-', action, '' if ok else 'NOT WHAT WAS INTENDED'))
    print('   %d of %d tests give the wrong answer' % (wrong, len(TESTS)))
    # aim one sample packet at each rule and see which rule actually catches it
    dead = []
    for rule in rules:
        sample_src = '10.21.99.5' if rule[2] == EXAM else (
            '10.21.7.2' if rule[2] == '10.21.7.0/24' else (
                '10.21.4.9' if rule[2] == INSIDE else '198.51.100.4'))
        sample_dst = '10.21.50.8' if rule[3] == SERVERS else '203.0.113.5'
        sample_port = sorted(rule[5])[0] if isinstance(rule[5], set) else 443
        hit, _, _ = evaluate(rules, (sample_src, sample_dst, 'tcp', sample_port))
        if hit != rule[0]:
            dead.append((rule[0], hit))
    if dead:
        for number, hit in dead:
            print('   rule %d can never fire: rule %s catches its traffic first' % (number, hit))
    else:
        print('   every rule can fire')
    print()
munotes.in635

Firewall Configurations, and a Rule Base Read Line by Line

AS FIRST WRITTEN
   a visitor reads the college website    rule 1    allow
   a student browses the web              rule 2    allow
   an examination machine reaches out     rule 2    allow  NOT WHAT WAS INTENDED
   an administrator uses ssh              rule 2    allow
   a stranger tries ssh on a server       rule 4    deny
   a stranger tries the database          rule 4    deny
   1 of 6 tests give the wrong answer
   rule 3 can never fire: rule 2 catches its traffic first
   rule 5 can never fire: rule 4 catches its traffic first

AFTER REORDERING
   a visitor reads the college website    rule 2    allow
   a student browses the web              rule 4    allow
   an examination machine reaches out     rule 1    deny
   an administrator uses ssh              rule 3    allow
   a stranger tries ssh on a server       rule 5    deny
   a stranger tries the database          rule 5    deny
   0 of 6 tests give the wrong answer
   every rule can fire

What the run establishes, in order.

One rule in the wrong place opens the firewall quietly. The rule forbidding the examination network to reach the Internet is rule 3, below the rule permitting the whole inside network to go out. The examination machine is allowed out by rule 2, and rule 3 never runs. Nothing in the file looks wrong; the test does.

munotes.in636

Firewall Configurations, and a Rule Base Read Line by Line

A rule below the deny-all is dead. The administrators' ssh rule is rule 5, after "deny everything else". The run reports that rule 4 catches its traffic first. In practice this shows up as administrators complaining that ssh does not work, and the usual response, adding another rule at the bottom, does nothing.

Reordering fixes both, and the tests say so. With the specific deny at the top and the deny-all at the bottom, all six tests give the intended verdict and every rule can fire. The rule permitting ssh was also narrowed to the administrators' own network, which the first version never even reached.

This is what "test the rules" means in the practical. Not that the firewall starts, but that a written list of intended verdicts is put to it and every one comes back as intended.

Writing a rule base that can be maintained

  1. Start from deny. Write the last rule first.
  2. Put the narrow rules above the broad ones. Specific denies at the top; broad allows below.
  3. Say why in every rule, and who asked for it, and when.
  4. Write the tests with the rules, including the packets that must be refused; those are the ones nobody tests.
  5. Check for unreachable rules whenever the file changes.
  6. Log the denies, at least by sample, or you will never learn what the policy is costing.
  7. Filter outbound too, at least so that only your own source addresses leave.
  8. Review on a date, because a rule added for a project outlives the project.

Distinctions that carry marks

Single-homed bastionDual-homed bastionScreened subnet
Routersoneonetwo
Path round the bastionexists if the router is wrongnonenone
Inside addresses visible outsideyesyesno
Levels to defeattwotwo, with no bypassthree
Where public servers goon the internal networkon the internal networkon the DMZ
First match winsEvery rule evaluated
Real firewallsyesno
Consequenceorder is part of the policyorder would not matter
Typical faulta rule that can never firenone

What beginners get wrong here

Reading a rule base as a set of statements. It is a sequence; the same rules in a different order are a different policy.

Putting the deny-all first. Then nothing works, which at least is obvious; the dangerous mistake is the one that fails open.

Testing only what should work. The rules that matter are the ones that must refuse, and a firewall that allows everything passes every test of the first kind.

munotes.in637

Firewall Configurations, and a Rule Base Read Line by Line

Thinking the screened subnet is just two firewalls. Its point is the network between them, where public servers can be reached from outside without putting them inside.

Leaving a rule because nobody knows what it does. That is what the comment column is for.

Quick revision

  • Screened host, single-homed bastion: one router, bastion on the internal network; a wrong router rule exposes the inside hosts.
  • Screened host, dual-homed bastion: bastion has two interfaces; no physical path round it.
  • Screened subnet: two routers with a subnet between them, the DMZ, where public servers live; inside addresses never advertised outside; most secure.
  • Bastion host: hardened, minimal services, least privilege, heavy logging.
  • A rule base is read top down, first match wins, ending in deny all.
  • Two ordering mistakes: a broad allow above a specific deny; a rule below the deny-all.
  • Test with a written list of intended verdicts, including what must be refused, and check that every rule can fire.

Test yourself

1. Describe the three firewall configurations. In the screened host firewall with a single-homed bastion, a packet-filtering router permits traffic from the Internet only to the bastion host and permits outbound traffic only from it, while the bastion sits on the internal network; an attacker must defeat both the router and the bastion, but a mistake in the router's rules can expose the internal hosts directly. In the screened host firewall with a dual-homed bastion, the bastion has two network interfaces and the internal network is physically separate from the router's side, so traffic cannot reach the inside except through the bastion whatever the router does. In the screened subnet configuration, two packet-filtering routers are used with an isolated subnetwork between them on which the bastion and the public servers sit; it gives three levels to defeat, keeps the internal addresses invisible from the Internet, and is the most secure of the three.

2. What is a DMZ and what is put on it? A DMZ is the subnetwork between the outer and inner routers of a screened subnet configuration, reachable from the Internet under the outer router's rules but separated from the internal network by the inner router. Public-facing servers such as web and mail servers are placed there, so that they can be reached from outside without giving the outside any path to the internal network; if such a server is compromised, the attacker is still outside the inner router.

3. Explain how a firewall reads a rule base, and what follows from it. It reads the rules in order from the top and applies the first rule whose conditions the packet matches, ignoring everything below it; the last rule denies everything, so that whatever nobody anticipated is refused. It follows that the order of the rules is part of the policy: a rule placed below a broader rule that also matches its traffic will never run, and a rule placed below the final deny-all can never run at all. Both faults leave the file looking correct while the firewall behaves differently from what the file appears to say.

munotes.in638

Firewall Configurations, and a Rule Base Read Line by Line

4. Give the two common ordering mistakes in a rule base, with their effect. The first is placing a broad allow above a specific deny: for example, allowing the whole internal network out before denying the examination network, so that examination traffic matches the allow and the deny never runs, and the firewall is open where the policy says it is closed. The second is placing a rule below the final deny-all: for example, adding an administrators' ssh rule at the bottom of the file, where the deny has already matched everything, so the rule can never fire and the access it was meant to grant does not work.

5. How would you test a firewall rule base? By writing, alongside the rules, a list of test cases with the verdict each is meant to receive, including both traffic that must be allowed and, more importantly, traffic that must be refused; then putting each case to the firewall and comparing the verdict and the rule number that produced it with what was intended. In addition, each rule should be probed with traffic it is meant to catch, to reveal any rule that can never fire because an earlier rule matches first. Any difference is a fault in the rules or in the policy, and the tests are rerun whenever the file changes.

Contents This chapter on its own page

munotes.in639

Chapter Ninety-Six

Practical: Signing and Verifying a Message in Code

Syllabus topic Practical 5, "Implement digital signature algorithms such as RSA-based signatures"

In one line

Signing is hashing the message and then doing the private-key operation on the hash; verifying is doing the public-key operation on the signature and checking that what comes out is the hash of the message in front of you.

Aim

To write a program that signs a message with a private key, verifies the signature with the corresponding public key, and demonstrates that any change to the message or to the signature makes verification fail.

What is needed

  • A computer with Python 3 installed. Nothing else: the program uses only hashlib and random from the standard library.
  • Optionally OpenSSL, to check the result against an independent implementation.

Theory in four lines

  • A digital signature gives integrity, authentication of origin and non-repudiation: only the holder of the private key could have produced it, and anyone with the public key can check.
  • The message is hashed first, so that the signature is a fixed size whatever the message's length, and so that the operation is on 32 bytes rather than on megabytes.
  • The hash is padded to the width of the modulus before the private-key operation. The padding is not decoration: signing a bare hash is insecure, and RFC 8017's EMSA-PKCS1-v1_5 is the scheme that fills the block.
  • Signing uses the private key; verifying uses the public one. That is the whole difference from encryption, where the public key encrypts.

Procedure

  1. Generate an RSA key pair: two large primes, their product n, the public exponent e, and the private exponent d.
  2. Take the message and compute its SHA-256 digest.
  3. Build the padded block and raise it to the power d modulo n: that is the signature.
  4. Verify by raising the signature to the power e modulo n and comparing the result with the block built from the hash of the message you have.
  5. Change one character of the message and verify again. Then change one bit of the signature and verify again.
  6. Record every output in the journal.

The program and its output

# Practical 5, exercise 4: sign a message, verify it, then change one character and watch the
# verification fail. Standard library only, so it runs on any machine with Python.
import hashlib, random

# ---- step 1: a key pair. A real one is made by a library; this one is built here so that
# the whole practical can be read, and seeded so that the journal prints the same numbers twice.
rng = random.Random(2026)

def probably_prime(n, rounds=40):
    if n < 2 or n % 2 == 0:
        return n == 2
    d, s = n - 1, 0
    while d % 2 == 0:
        d, s = d // 2, s + 1
    for _ in range(rounds):                       # Miller-Rabin
        x = pow(rng.randrange(2, n - 1), d, n)
        if x in (1, n - 1):
            continue
        for _ in range(s - 1):
            x = x * x % n
            if x == n - 1:
                break
        else:
            return False
    return True

def prime(bits):
    while True:
        candidate = rng.getrandbits(bits) | (3 << bits - 2) | 1
        if probably_prime(candidate):
            return candidate

e = 65537
while True:
    p, q = prime(1024), prime(1024)
    if p != q and (p - 1) * (q - 1) % e:
        break
n = p * q
d = pow(e, -1, (p - 1) * (q - 1))
K = n.bit_length() // 8
print('STEP 1  a %d-bit RSA key pair' % n.bit_length())
print('        public  (n, e), e = %d, n has %d digits' % (e, len(str(n))))
print('        private (n, d), which never leaves this program')

# ---- step 2: the message ----------------------------------------------------------------------
message = b'Pay Rs 5,000 to the sports committee on 5 October 2026.'
digest = hashlib.sha256(message).digest()
print()
print('STEP 2  the message and its digest')
print('        message: %s' % message.decode())
print('        SHA-256: %s' % digest.hex())

# ---- step 3: sign the digest, in the padding RFC 8017 s.9.2 defines ----------------------------
SHA256_PREFIX = bytes.fromhex('3031300d060960864801650304020105000420')

def encoded(dgst):
    """EMSA-PKCS1-v1_5: 00 01 FF...FF 00 || DigestInfo, filling the modulus exactly."""
    tail = SHA256_PREFIX + dgst
    return b'\x00\x01' + b'\xff' * (K - len(tail) - 3) + b'\x00' + tail

signature = pow(int.from_bytes(encoded(digest), 'big'), d, n).to_bytes(K, 'big')
print()
print('STEP 3  the signature')
print('        %d bytes: %s...%s' % (len(signature), signature.hex()[:32], signature.hex()[-16:]))

# ---- step 4: verify, using only the public key --------------------------------------------------
def verify(msg, sig):
    """Open the signature with the public key and compare it with the block we expect."""
    opened = pow(int.from_bytes(sig, 'big'), e, n).to_bytes(K, 'big')
    return opened == encoded(hashlib.sha256(msg).digest())

print()
print('STEP 4  verification with the PUBLIC key only')
print('        the message as sent verifies: %s' % verify(message, signature))

# ---- step 5: tamper, and watch it fail -----------------------------------------------------------
altered = message.replace(b'5,000', b'50,000')
assert altered != message
print()
print('STEP 5  change one character and try again')
print('        altered: %s' % altered.decode())
print('        SHA-256: %s' % hashlib.sha256(altered).hexdigest())
print('        the altered message verifies: %s' % verify(altered, signature))
flipped = bytearray(signature)
flipped[-1] ^= 1
assert bytes(flipped) != signature
print('        one bit changed in the signature verifies: %s'
      % verify(message, bytes(flipped)))
print()
print('WHAT THE JOURNAL RECORDS: the signature is %d bytes whatever the message is,'
      % len(signature))
print('it was made with the private key and checked with the public one, and any change')
print('to either the message or the signature makes the check fail.')
munotes.in640

Practical: Signing and Verifying a Message in Code

STEP 1  a 2048-bit RSA key pair
        public  (n, e), e = 65537, n has 617 digits
        private (n, d), which never leaves this program

STEP 2  the message and its digest
        message: Pay Rs 5,000 to the sports committee on 5 October 2026.
        SHA-256: bdac49eb1fb9bd78c3e2d53d7315964fdc0c17b6c0ff3dd63f87d6380ff501ea

STEP 3  the signature
        256 bytes: a199d30da9007ba0c3f4c18ab2ce6c02...8848af5a3b0a8f31

STEP 4  verification with the PUBLIC key only
        the message as sent verifies: True

STEP 5  change one character and try again
        altered: Pay Rs 50,000 to the sports committee on 5 October 2026.
        SHA-256: c4adb3a7b3c74ada0135318814326611e4836a34d12d4c4bda047b10918a191f
        the altered message verifies: False
        one bit changed in the signature verifies: False

WHAT THE JOURNAL RECORDS: the signature is 256 bytes whatever the message is,
it was made with the private key and checked with the public one, and any change
to either the message or the signature makes the check fail.
munotes.in641

Practical: Signing and Verifying a Message in Code

What the output proves

The signature is the same size whatever the message is. 256 bytes, because it is one number modulo a 2048-bit n. A signature on a book would also be 256 bytes, which is why the message is hashed first.

Verification uses only the public key. Nothing secret is needed to check a signature, which is what makes it publishable and what makes non-repudiation possible.

Changing the message breaks it. "5,000" became "50,000", the digest changed completely, and the check failed. The verifier does not have to know what was changed or where; it only has to know that what it hashes is not what was signed.

Changing the signature breaks it too. One bit flipped in the last byte, and the block that comes out of the public-key operation is no longer a valid padded digest.

An independent implementation agrees. The same public key and signature were put to OpenSSL, which answered "Verified OK" for the message as sent and refused the altered one.

Questions the examiner asks

Why hash before signing? For size, so that the signature is fixed and small; for speed, since the public-key operation is expensive; and for safety, since the padded hash block is what the security proof is about.

Why is padding needed? Because the plain hash occupies a small part of the block, and leaving the rest empty allows attacks on the arithmetic. The padding fills the modulus exactly and marks where the digest begins, so a verifier can tell a real signature from a number that happens to work.

What if the private key is lost or stolen? Lost: nothing more can be signed, and a new key must be published. Stolen: everything it signed becomes doubtful, and the certificate must be revoked, which is what the CRL and OCSP chapter is about.

Can you sign with the public key? No. The private key is the one only the signer has; that is precisely what makes a signature evidence.

munotes.in642

Practical: Signing and Verifying a Message in Code

Common mistakes in the laboratory

  • Signing the message instead of the hash for a long message: slow, and the wrong construction.
  • Comparing hashes instead of verifying: comparing a hash proves nothing about who produced it.
  • Using the same key pair for signing and for encryption without thinking: the standards keep them separate, as the certificate chapters showed.
  • Testing only the case that works. A verification function that returns True unconditionally passes that test.
  • Leaving the private key in a file next to the program, which in a real system is the whole attack.

Quick revision

  • Sign: hash, pad (RFC 8017 EMSA-PKCS1-v1_5), private-key operation.
  • Verify: public-key operation, rebuild the block from the hash of the message you have, compare.
  • Signature length follows the modulus, not the message.
  • Any change to message or signature makes verification fail.
  • Services: integrity, authentication, non-repudiation.

Test yourself

1. Write the steps of signing and verifying an RSA signature. To sign: compute the hash of the message, usually SHA-256; encode that hash into a block the width of the modulus, using the EMSA-PKCS1-v1_5 padding of RFC 8017, which places 0x00 0x01, a run of 0xFF bytes, 0x00 and then the DigestInfo containing the algorithm identifier and the hash; interpret the block as an integer and raise it to the private exponent d modulo n. To verify: raise the signature to the public exponent e modulo n, build the same padded block from the hash of the message received, and compare the two; equal means the signature is valid.

2. Why is the message hashed before it is signed? Because the public-key operation works on a number smaller than the modulus, so a long message could not be signed directly; because the operation is slow and hashing reduces any message to a fixed 32 bytes; and because the signature should be of fixed size no matter what is signed. Hashing also means the signature commits to the whole message, since any change alters the digest.

3. What does a failed verification tell you, and what does it not tell you? It tells you that the message you hold, with the signature you hold, is not the pair that was signed by the holder of that private key: something has changed, or the signature was made with a different key. It does not tell you what was changed, where, or by whom, and it does not distinguish an altered message from an altered signature or from a signature made by someone else entirely.

4. What security services does a digital signature provide? Integrity, because any change to the message makes verification fail; authentication of origin, because only the holder of the private key could produce a signature that verifies with the matching public key; and non-repudiation, because that evidence can be shown to a third party, so the signer cannot later deny having signed. It does not provide confidentiality: the message is not hidden by being signed.

munotes.in643

Practical: Signing and Verifying a Message in Code

5. How would you check that your implementation is correct? By verifying its output with an independent implementation rather than with itself: write out the public key in a standard form and let another tool, such as OpenSSL, verify the signature the program produced, and confirm that the same tool rejects an altered message. Testing only the success case is not enough, so the journal should record both a verification that succeeds and verifications that fail after the message and after the signature have been changed.

Contents This chapter on its own page

munotes.in644

Chapter Ninety-Seven

Practical: Configuring IPsec, and Reading the Policy Back

Syllabus topic Practical 5, "IP Security (IPsec) Configuration"

In one line

Configuring IPsec is writing a policy that says which traffic must be protected, which may pass in clear and which must be dropped, and then proving from the packets that the policy is in force.

Aim

To configure IPsec between two hosts so that traffic between them is protected, to read the resulting policy and security associations back from the system, and to demonstrate from the packets themselves that protection is being applied.

What is needed

  • Two machines, or one machine and a virtual one, that can reach each other: call them the client 10.21.4.9 and the server 10.21.50.8.
  • Administrator rights on both, and a packet capture tool such as tcpdump or Wireshark.
  • On Linux, the ip xfrm commands; on Windows, the IP Security Policy snap-in; the shapes are the same.

Theory in four lines

  • The Security Policy Database (SPD) says, for each kind of traffic, whether to protect it, bypass it or discard it.
  • The Security Association Database (SAD) holds the actual associations: for each, an SPI, the algorithms and the keys.
  • A security association is one way, so a two-way conversation needs two, with different SPIs.
  • Transport mode protects the payload of a packet between two hosts; tunnel mode wraps the whole packet in a new one, which is what gateways do.

Procedure

  1. On both machines, define the policy: protect traffic between the two addresses, allow the rest of the campus in clear, and discard anything else leaving.
  2. Create the security associations, one in each direction, with different SPIs and their own keys.
  3. Read the policy and the associations back and check them against what was intended, line by line.
  4. Generate some traffic between the two machines.
  5. Capture the packets and look at them: the protocol number, the SPI, the sequence number, and whether the data is readable.
  6. Check the association's counters: they rise only if the association is being used.
  7. Record all of it in the journal, including the capture.

What the commands look like

On Linux the policy and the associations are written like this, and read back with the matching show commands. The keys below are examples, and in a real system come from IKE rather than from the keyboard.

# on the client 10.21.4.9, for traffic to the server 10.21.50.8
ip xfrm policy add dir out src 10.21.4.9/32 dst 10.21.50.8/32 \
    tmpl proto esp mode transport
ip xfrm policy add dir in  src 10.21.50.8/32 dst 10.21.4.9/32 \
    tmpl proto esp mode transport

# one association for each direction, with different SPIs
ip xfrm state add src 10.21.4.9 dst 10.21.50.8 proto esp spi 0x2A01 \
    mode transport enc 'cbc(aes)' 0x... auth 'hmac(sha256)' 0x...
ip xfrm state add src 10.21.50.8 dst 10.21.4.9 proto esp spi 0x5B01 \
    mode transport enc 'cbc(aes)' 0x... auth 'hmac(sha256)' 0x...

# read back what the kernel actually holds
ip xfrm policy show
ip xfrm state show
munotes.in645

Practical: Configuring IPsec, and Reading the Policy Back

Read the output of the last two commands, not the absence of an error from the first four. A policy can be accepted and still not match the traffic you meant, and an association with the wrong address is simply never used.

The program: the policy applied, and the packets compared

The listing takes the same policy, in the form the kernel keeps it, decides what happens to four packets, and then builds the packet that leaves the machine with and without the policy, so that the two can be compared.

# Practical 5, exercise 6: read the policy back, then prove from the packets that it is applied.
# The policy below is the one in the configuration, typed out again in the form the kernel keeps
# it. Nothing is sent: the packets are byte strings built here.
import hashlib, ipaddress

# ---- the policy, as the configuration wrote it -------------------------------------------------
# direction, source network, destination network, upper protocol, what to do
POLICY = [
    ('out', '10.21.4.9/32', '10.21.50.8/32', 'any', 'protect: esp transport, SPI 0x2A01'),
    ('in', '10.21.50.8/32', '10.21.4.9/32', 'any', 'protect: esp transport, SPI 0x5B01'),
    ('out', '10.21.4.9/32', '10.21.0.0/16', 'any', 'bypass: the rest of the campus in clear'),
    ('out', '10.21.4.9/32', '0.0.0.0/0', 'any', 'discard: nothing else leaves this host'),
]
SPI_OUT, SPI_IN = 0x2A01, 0x5B01

def lookup(direction, src, dst):
    """Ordered, first match wins: the same rule a firewall follows, and the kernel too."""
    for d, s_net, d_net, proto, action in POLICY:
        if (d == direction and ipaddress.ip_address(src) in ipaddress.ip_network(s_net)
                and ipaddress.ip_address(dst) in ipaddress.ip_network(d_net)):
            return action
    return 'discard: no policy matched'

print('1. THE POLICY, READ BACK')
for d, s_net, d_net, proto, action in POLICY:
    print('   %-4s %-16s to %-16s %s' % (d, s_net, d_net, action))

print()
print('2. WHAT HAPPENS TO EACH PACKET')
TRAFFIC = [('out', '10.21.4.9', '10.21.50.8', 'to the server the policy protects'),
           ('out', '10.21.4.9', '10.21.60.2', 'to another campus machine'),
           ('out', '10.21.4.9', '203.0.113.5', 'to the Internet'),
           ('in', '10.21.50.8', '10.21.4.9', 'the server answering')]
for direction, src, dst, what in TRAFFIC:
    print('   %-4s %-12s to %-12s %-34s %s'
          % (direction, src, dst, what, lookup(direction, src, dst).split(':')[0].upper()))

# ---- 3. build the packet the policy asks for, and look at what is visible ----------------------
PAYLOAD = b'GET /marks/rollno/4417 HTTP/1.1\r\nHost: results.example.edu\r\n\r\n'

def keystream(key, spi, seq, length):
    """A stand-in for the cipher the two ends negotiate. The practical's point is the
    headers and what remains readable, not which cipher was chosen."""
    out = b''
    while len(out) < length:
        out += hashlib.sha256(key + spi.to_bytes(4, 'big') + seq.to_bytes(4, 'big')
                              + len(out).to_bytes(4, 'big')).digest()
    return out[:length]

def esp_transport(payload, spi, seq, key):
    """RFC 4303: outer IP header, then SPI, sequence number, the encrypted part, then the ICV."""
    pad = (-(len(payload) + 2)) % 4
    body = payload + bytes(range(1, pad + 1)) + bytes([pad]) + bytes([6])   # 6 = TCP
    encrypted = bytes(a ^ b for a, b in zip(body, keystream(key, spi, seq, len(body))))
    header = spi.to_bytes(4, 'big') + seq.to_bytes(4, 'big')
    icv = hashlib.sha256(key + header + encrypted).digest()[:12]
    ip = bytes([0x45, 0x00]) + (20 + len(header + encrypted + icv)).to_bytes(2, 'big')
    ip += bytes([0, 0, 0, 0, 64, 50])                       # protocol 50 is ESP
    ip += bytes([0, 0]) + ipaddress.ip_address('10.21.4.9').packed
    ip += ipaddress.ip_address('10.21.50.8').packed
    return ip + header + encrypted + icv

KEY = b'the key IKE negotiated, kept by the two hosts only'
protected = esp_transport(PAYLOAD, SPI_OUT, 1, KEY)
plain = (bytes([0x45, 0x00]) + (20 + len(PAYLOAD)).to_bytes(2, 'big')
         + bytes([0, 0, 0, 0, 64, 6, 0, 0]) + ipaddress.ip_address('10.21.4.9').packed
         + ipaddress.ip_address('10.21.50.8').packed + PAYLOAD)

print()
print('3. THE TWO PACKETS ON THE WIRE')
for name, packet in (('without the policy', plain), ('with the policy', protected)):
    print('   %-20s %3d bytes, protocol %-3d %s'
          % (name, len(packet), packet[9],
             'ESP' if packet[9] == 50 else 'TCP, carried in the clear'))
    print('      the roll number is readable in it: %s' % (b'4417' in packet))
    print('      the first 16 bytes after the IP header: %s' % packet[20:36].hex())

print()
print('4. THE TEST THAT PROVES NOTHING, AND THE ONE THAT DOES')
print('   "the connection still works" is true of both packets above')
print('   "protocol 50 and the data is not readable" is true of only one')
print('   counters rise only when a security association is actually used:')
for seq in (1, 2, 3):
    packet = esp_transport(PAYLOAD, SPI_OUT, seq, KEY)
    print('      SPI 0x%04X sequence %d, %d bytes'
          % (int.from_bytes(packet[20:24], 'big'), int.from_bytes(packet[24:28], 'big'),
             len(packet)))
munotes.in646

Practical: Configuring IPsec, and Reading the Policy Back

1. THE POLICY, READ BACK
   out  10.21.4.9/32     to 10.21.50.8/32    protect: esp transport, SPI 0x2A01
   in   10.21.50.8/32    to 10.21.4.9/32     protect: esp transport, SPI 0x5B01
   out  10.21.4.9/32     to 10.21.0.0/16     bypass: the rest of the campus in clear
   out  10.21.4.9/32     to 0.0.0.0/0        discard: nothing else leaves this host

2. WHAT HAPPENS TO EACH PACKET
   out  10.21.4.9    to 10.21.50.8   to the server the policy protects  PROTECT
   out  10.21.4.9    to 10.21.60.2   to another campus machine          BYPASS
   out  10.21.4.9    to 203.0.113.5  to the Internet                    DISCARD
   in   10.21.50.8   to 10.21.4.9    the server answering               PROTECT

3. THE TWO PACKETS ON THE WIRE
   without the policy    82 bytes, protocol 6   TCP, carried in the clear
      the roll number is readable in it: True
      the first 16 bytes after the IP header: 474554202f6d61726b732f726f6c6c6e
   with the policy      104 bytes, protocol 50  ESP
      the roll number is readable in it: False
      the first 16 bytes after the IP header: 00002a01000000018ef2ca283e1550e9

4. THE TEST THAT PROVES NOTHING, AND THE ONE THAT DOES
   "the connection still works" is true of both packets above
   "protocol 50 and the data is not readable" is true of only one
   counters rise only when a security association is actually used:
      SPI 0x2A01 sequence 1, 104 bytes
      SPI 0x2A01 sequence 2, 104 bytes
      SPI 0x2A01 sequence 3, 104 bytes
munotes.in647

Practical: Configuring IPsec, and Reading the Policy Back

What the output proves

The policy is ordered, and the order decides. The rule protecting traffic to the server is above the rule that lets the rest of the campus pass in clear, so the protected case is matched first. Written the other way round, everything would go in clear and nothing would report an error.

Three outcomes, not two. Traffic to the server is protected, traffic to the rest of the campus bypasses, traffic to the Internet is discarded. Discard is a policy decision, not a failure, and it is the one students most often forget to test.

The protected packet is a different packet. Its IP header says protocol 50, ESP; it carries an SPI and a sequence number in the clear, because the receiver needs them to find the right association; and the roll number that was plainly visible in the unprotected packet cannot be found in it. It is also longer, by the ESP header, the padding and the integrity check value.

The sequence number rises. Successive packets on the same association carry 1, 2, 3, which is what the anti-replay window checks and what makes the counters on the association meaningful.

Questions the examiner asks

Why two security associations? Because an association is one-way. Each direction has its own SPI, its own keys and its own sequence numbers.

What is the SPI for? The receiver uses it, with the destination address and the protocol, to find which association to use. It is not secret, and it is not the same in both directions.

Transport or tunnel mode here? Transport, because the two ends are the hosts themselves. Tunnel mode is for gateways, where the whole original packet must be hidden inside a new one.

How do you know it is working? From the packets: protocol 50, an SPI you recognise, a rising sequence number, and data that is no longer readable. Not from the fact that the application still works.

Common mistakes in the laboratory

  • Configuring one side only. The other end discards what it cannot verify, and the symptom is a silence, not an error.
  • Using the same SPI in both directions, or the same key, which the IPsec chapter recorded as a fault worth catching.
  • Putting the bypass rule above the protect rule, so that nothing is ever protected and nothing complains.
  • Forgetting the inbound policy, so protected replies are dropped.
  • Testing with ping only. ICMP may match a different rule than the traffic you care about.
  • Believing a capture taken on the sending machine: on some systems the capture point is before IPsec is applied, so capture on the wire or on the receiver as well.
munotes.in648

Practical: Configuring IPsec, and Reading the Policy Back

Quick revision

  • SPD: protect, bypass, discard, in order, first match wins. SAD: SPI, algorithms, keys.
  • A security association is one way: two are needed, with different SPIs.
  • Transport mode between hosts; tunnel mode between gateways.
  • Read back with ip xfrm policy show and ip xfrm state show.
  • Proof is in the packet: protocol 50, SPI, sequence number, payload not readable, length grown.
  • "It still works" is not proof; a policy that never matches also still works.

Test yourself

1. What three actions can an IPsec policy take, and how are they chosen? A policy entry can protect the traffic, applying AH or ESP with a security association; bypass it, letting it pass without IPsec; or discard it. The entries are searched in order and the first that matches the packet's selectors, such as source and destination addresses, protocol and ports, decides, so the order of entries is part of the policy.

2. Why are two security associations needed between two hosts, and what distinguishes them? Because a security association is unidirectional: it protects traffic in one direction only. The pair is distinguished by the SPI, by the destination address, and normally by separate keys and independent sequence numbers, so that the two directions cannot be confused and replaying a packet from one direction into the other is impossible.

3. How would you prove that your IPsec configuration is actually in use? By capturing the traffic and examining the packets: the IP header's protocol field should be 50 for ESP or 51 for AH, the packet should carry the SPI configured for that direction and a sequence number that rises with each packet, and the data that was readable without IPsec should no longer be found in it. The counters on the security association should also rise. The fact that the application still works proves nothing, because a policy that is never matched leaves the traffic exactly as it was.

4. Write the policy for a host that must protect its traffic to one server, reach the rest of its campus in clear, and send nothing else. Three outbound entries in this order: first, for source the host and destination the server, protect with ESP in transport mode; second, for source the host and destination the campus network, bypass; third, for source the host and any destination, discard. A matching inbound entry protects the traffic coming back from the server. If the second entry were placed first, traffic to the server would match it and go in clear.

munotes.in649

Practical: Configuring IPsec, and Reading the Policy Back

5. What goes wrong if you configure only one end? The configured end protects its outgoing traffic, and the other end receives packets with protocol 50 for which it has no security association, so it discards them; traffic in the other direction arrives unprotected at the configured end, whose inbound policy requires protection, so it is discarded too. The result is that the connection fails silently in both directions, with no error message that names the cause, which is why the policy and the associations must be read back on both machines.

Contents This chapter on its own page

munotes.in650

Chapter Ninety-Eight

Practical: Certificates and a Secure Session, End to End

Syllabus topic Practical 5, "including certificate management and secure session establishment"

In one line

Making a certificate and serving over TLS is easy; the practical's point is that encryption and identity are different things, and a self-signed certificate gives you the first without the second.

Aim

To generate a key pair and a certificate, serve a TLS session with them, then read the certificate back field by field and run the checks a client runs, showing which pass and which does not.

What is needed

  • OpenSSL, on any operating system.
  • Python 3 for the reading-back program. Nothing else.
  • A free port to listen on: 44398 is used here.

Theory in four lines

  • A certificate binds a name to a public key, and is signed by an issuer.
  • A client trusts a certificate when it can build a chain from it to a root it already trusts, and when the name, the dates and the signatures are all right.
  • A self-signed certificate is its own issuer, so the chain ends where it began and there is nothing to trust.
  • TLS then uses the key in the certificate to prove that the server holds the matching private key, and to agree session keys. Encryption without identity is still a private conversation with a stranger.

Procedure

  1. Generate a key pair and a self-signed certificate naming the server.
  2. Start a TLS server using them.
  3. Connect with a client, and record the protocol version, the cipher suite and the verification result.
  4. Read the certificate back: subject, issuer, serial number, validity, the names it covers, the key, the signature algorithm.
  5. Run the four checks a client runs, and see which fails.
  6. Record all of it, including the failure, in the journal.

The commands, and what they said

# 1. a key pair and a certificate that names the server, valid for a year
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes \
    -subj "/CN=exam.college.example/O=Example College/C=IN" \
    -addext "subjectAltName=DNS:exam.college.example"

# 2. serve with them
openssl s_server -cert cert.pem -key key.pem -accept 44398 -www

# 3. connect, and read what the client made of it
openssl s_client -connect 127.0.0.1:44398 -servername exam.college.example -showcerts

What the client reported:

subject=CN=exam.college.example, O=Example College, C=IN
issuer=CN=exam.college.example, O=Example College, C=IN
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Verify return code: 18 (self-signed certificate)

Read the last line before celebrating the first three. The session is TLS 1.3 with a strong cipher suite, and the identity behind it has not been established at all.

The program: reading the certificate back

The listing takes the certificate the server presented and reads it from the bytes up: it walks the DER, prints what the certificate says, and runs the checks a client runs, including verifying the signature itself.

-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIUXxxy5QtLlUG3jt6WglloPWjkRR8wDQYJKoZIhvcNAQEL
BQAwRjEdMBsGA1UEAwwUZXhhbS5jb2xsZWdlLmV4YW1wbGUxGDAWBgNVBAoMD0V4
YW1wbGUgQ29sbGVnZTELMAkGA1UEBhMCSU4wHhcNMjYwOTMwMTI1MjQzWhcNMjcw
OTMwMTI1MjQzWjBGMR0wGwYDVQQDDBRleGFtLmNvbGxlZ2UuZXhhbXBsZTEYMBYG
A1UECgwPRXhhbXBsZSBDb2xsZWdlMQswCQYDVQQGEwJJTjCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAI7xyrE1VicqjN0ffWJUzYVRsGMxaqI6vfpgJAWE
GaPk/XHD9ibIHt+S+4wHKZIORR7SabGB0SMLcYFnZk+S4ZnjOUkXYovw2F6vWGHx
jQ3Opp7Wcm/XojnF51sXpsp+Blbn2O1o0IDM+sPio8PaEMHPPT0V6uMkbsv7QEc2
doMuftI1Ubru+nLDvPi3rW+ycaNoi7GqAbur5jgb/NzJ7Wzlf3FPxb/q/gAQUpdt
JSE7OxFg1lF8+wnzcEEoHN2JZWL9eOSDRKgdBi+Ze6j+Z82/roc//G8Pb623oVUo
TgS7e+JOqPxyJcaxdn8ssBvkrs3JlD7YVEzX54E1omZwdukCAwEAAaN0MHIwHQYD
VR0OBBYEFEz2vP+p1JSi2yCVkp0rryoV5FPDMB8GA1UdIwQYMBaAFEz2vP+p1JSi
2yCVkp0rryoV5FPDMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0RBBgwFoIUZXhhbS5j
b2xsZWdlLmV4YW1wbGUwDQYJKoZIhvcNAQELBQADggEBAG5mg9UJNWfgDxrVKUYy
FQQ8KNAtmdbCAv/vqHRGNQVUnwUhNVL6w4pDq1DNsD5cZu0RLm801NBpTgKYFbMV
xsqJiqSoZOLzaJQfeA+iVYroxqwlLSEM5nAOKqViH9iS/I9hJAjzcCniZtZfQGLI
0foGbW/d+V7lz8kGubigbbibWs64EnJgoJc2DgjcGsGhszTysGG9l+eZPYWjlFP8
HmEoC8eBIYzbzwTZm/TeoMDw9+0uD5XJrz5PlyETcPgCOa7uziNPxzjSOfIakPmT
WqKs4rVWV541UgDkpvu75IsmjVcoU9yPjPSQMpgAZ1noxbEAoaJ757gahM8Q7tet
2cc=
-----END CERTIFICATE-----
munotes.in651

Practical: Certificates and a Secure Session, End to End

# Practical 5, exercise 7: read back the certificate the server presented, field by field,
# and run the checks a client runs. Standard library only; the file is the real one, made in
# the laboratory with openssl req, and served by openssl s_server.
import base64, datetime, hashlib

PEM = open('exam-cert.pem').read()
der = base64.b64decode(''.join(l for l in PEM.splitlines() if 'CERTIFICATE' not in l))

# ---- a DER reader: every field is a tag, a length and a value ---------------------------------
def parse(buf, at=0):
    """Return (tag, value bytes, index after this field)."""
    tag, length, at = buf[at], buf[at + 1], at + 2
    if length & 0x80:
        count = length & 0x7F
        length = int.from_bytes(buf[at:at + count], 'big')
        at += count
    return tag, buf[at:at + length], at + length

def items(buf):
    at = 0
    while at < len(buf):
        tag, value, at = parse(buf, at)
        yield tag, value

def as_int(value):
    return int.from_bytes(value, 'big')

OID_NAMES = {'550403': 'CN', '55040a': 'O', '550406': 'C'}
OID_SAN = '551d11'
OID_SHA256_RSA = '2a864886f70d01010b'

def read_name(value):
    """A Name is a sequence of sets of (OID, string)."""
    out = []
    for _, rdn in items(value):
        for _, pair in items(rdn):
            fields = [v for _, v in items(pair)]
            key = OID_NAMES.get(fields[0].hex(), fields[0].hex())
            out.append('%s=%s' % (key, fields[1].decode('utf-8', 'replace')))
    return ', '.join(out)

def read_time(value):
    stamp = value.decode()
    year = int(stamp[:2])
    return datetime.date(year + (2000 if year < 50 else 1900), int(stamp[2:4]), int(stamp[4:6]))

# certificate ::= SEQUENCE { tbsCertificate, signatureAlgorithm, signatureValue }
_, cert_body, _ = parse(der)
_, tbs_value, after_tbs = parse(cert_body)
tbs_bytes = cert_body[:after_tbs]                  # exactly the bytes that were signed
parts = list(items(cert_body))
sig_alg = parts[1][1]
signature = parts[2][1][1:]                        # BIT STRING, minus the unused-bits byte

fields = list(items(tbs_value))
offset = 1 if fields[0][0] == 0xA0 else 0                 # [0] EXPLICIT version, when present
serial = as_int(fields[offset][1])
issuer = read_name(fields[offset + 2][1])
not_before, not_after = [read_time(v) for _, v in items(fields[offset + 3][1])]
subject = read_name(fields[offset + 4][1])
spki = fields[offset + 5][1]
extensions = next((v for t, v in fields if t == 0xA3), b'')

# the public key: SEQUENCE { algorithm, BIT STRING { SEQUENCE { modulus, exponent } } }
key_bits = list(items(spki))[1][1][1:]
modulus, exponent = [as_int(v) for _, v in items(list(items(key_bits))[0][1])]

# subject alternative names: extnValue is an OCTET STRING wrapping GeneralNames
names = []
for _, extension_list in items(extensions):        # [3] EXPLICIT wraps a SEQUENCE
    for _, extension in items(extension_list):
        parsed = [v for _, v in items(extension)]
        if parsed[0].hex() != OID_SAN:
            continue
        _, general_names, _ = parse(parsed[-1])    # extnValue wraps GeneralNames
        names += [v.decode() for t, v in items(general_names) if t == 0x82]

print('1. THE FILE')
print('   %d bytes of DER inside the PEM, signed over %d of them' % (len(der), len(tbs_bytes)))
print('   SHA-256 fingerprint %s' % hashlib.sha256(der).hexdigest())

print()
print('2. WHAT IT SAYS')
print('   subject   %s' % subject)
print('   issuer    %s' % issuer)
print('   serial    %d' % serial)
print('   valid     %s to %s' % (not_before, not_after))
print('   names     %s' % (', '.join(names) or 'none given'))
print('   key       RSA, %d bits, exponent %d' % (modulus.bit_length(), exponent))
print('   signed with SHA-256 and RSA: %s'
      % (list(items(sig_alg))[0][1].hex() == OID_SHA256_RSA))

# ---- the checks a client makes -----------------------------------------------------------------
SHA256_PREFIX = bytes.fromhex('3031300d060960864801650304020105000420')

def signature_is_intact():
    """Open the signature with the key in this certificate and compare with the block
    RFC 8017 says should be there. For a self-signed certificate, the key is its own."""
    width = (modulus.bit_length() + 7) // 8
    opened = pow(as_int(signature), exponent, modulus).to_bytes(width, 'big')
    digest = hashlib.sha256(tbs_bytes).digest()
    tail = SHA256_PREFIX + digest
    expected = b'\x00\x01' + b'\xff' * (width - len(tail) - 3) + b'\x00' + tail
    return opened == expected

TODAY = datetime.date(2026, 9, 30)                # the day of the practical
WANTED = 'exam.college.example'
CHECKS = [('the name we asked for is in the certificate', WANTED in names),
          ('today falls inside the validity period', not_before <= TODAY <= not_after),
          ('the signature on it is intact', signature_is_intact()),
          ('the issuer is someone other than the subject', issuer != subject)]
print()
print('3. THE CHECKS A CLIENT MAKES')
for what, passed in CHECKS:
    print('   %-46s %s' % (what, 'yes' if passed else 'NO'))
passed = sum(ok for _, ok in CHECKS)
print()
print('   %d of %d pass. The one that fails is the one that matters: this certificate was'
      % (passed, len(CHECKS)))
print('   signed by its own key, so it vouches for nobody. openssl s_client said the same:')
print('   "Verify return code: 18 (self-signed certificate)". The session is encrypted;')
print('   the identity behind it is unproved.')
munotes.in652

Practical: Certificates and a Secure Session, End to End

1. THE FILE
   914 bytes of DER inside the PEM, signed over 634 of them
   SHA-256 fingerprint c98fa841cbcf2cce86c51165843730ad263de12d49543bf19c41628d161b0757

2. WHAT IT SAYS
   subject   CN=exam.college.example, O=Example College, C=IN
   issuer    CN=exam.college.example, O=Example College, C=IN
   serial    542988552834095815316831686345382359302521767199
   valid     2026-09-30 to 2027-09-30
   names     exam.college.example
   key       RSA, 2048 bits, exponent 65537
   signed with SHA-256 and RSA: True

3. THE CHECKS A CLIENT MAKES
   the name we asked for is in the certificate    yes
   today falls inside the validity period         yes
   the signature on it is intact                  yes
   the issuer is someone other than the subject   NO

   3 of 4 pass. The one that fails is the one that matters: this certificate was
   signed by its own key, so it vouches for nobody. openssl s_client said the same:
   "Verify return code: 18 (self-signed certificate)". The session is encrypted;
   the identity behind it is unproved.

What the output proves

A certificate is a structure, not a magic file. 914 bytes, of which 634 are the part that was signed. Every field can be read with a few lines of code: subject, issuer, serial, validity, the names it covers, the public key and the algorithm used to sign it.

munotes.in653

Practical: Certificates and a Secure Session, End to End

The signature is real and it checks out. The program opens the signature with the key in the certificate and compares the result with the block RFC 8017 says should be there. It matches, which proves the certificate has not been altered since it was signed.

Three checks pass and the fourth fails. The name is right, the dates are right, the signature is intact, and the issuer is the subject. That last failure is the whole point: nothing outside this file vouches for it. A browser would show a warning, and it would be right to.

OpenSSL agrees, in its own words. "Verify return code: 18 (self-signed certificate)."

How to make the fourth check pass

Two honest ways, and one dishonest one.

  • Be your own certification authority for a laboratory: make a CA key and certificate, sign the server certificate with it, and install the CA certificate as trusted on the client machines. The chain then has two links and the client can follow it. This is what an organisation does for its internal systems.
  • Get a certificate from a public CA, which will require you to prove control of the name.
  • Click through the warning, which is how people are trained to ignore the one check that detects an impostor.

Questions the examiner asks

Why did the client complain although the encryption was strong? Because encryption and authentication are separate. The handshake agreed keys with whoever presented the certificate; it did not establish who that was.

What would an attacker do against a self-signed setup? Present their own self-signed certificate. If users are used to clicking through warnings, nothing distinguishes the attacker from the real server.

Where does the private key go? Nowhere: it stays on the server, readable only by the account that needs it. In this practical it was deleted afterwards and never copied into the journal.

What is the subject alternative name for? It carries the names the certificate is valid for. Modern clients check it and ignore the common name for this purpose, so a certificate without it is rejected by browsers even if the common name matches.

Common mistakes in the laboratory

  • Recording the private key in the journal. Never; the key is the secret the whole thing rests on.
  • Omitting the subject alternative name and wondering why a browser refuses a certificate whose common name is correct.
  • Testing with the same machine's OpenSSL only, and never seeing a browser's warning, which is the more instructive failure.
  • Calling the exercise finished when the page loads. The verification result is part of the result.
  • Letting the certificate expire mid-semester and concluding that TLS is broken.
munotes.in654

Practical: Certificates and a Secure Session, End to End

Quick revision

  • openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes makes both.
  • openssl s_server -cert cert.pem -key key.pem -accept PORT -www serves; openssl s_client -connect HOST:PORT connects.
  • A client checks: name, dates, signature, chain to a trusted root.
  • Self-signed means issuer equals subject: the chain has one link and proves nothing.
  • Verify return code 18 is "self-signed certificate".
  • Encryption without identity is a private conversation with a stranger.

Test yourself

1. What does a certificate contain, and which parts are signed? It contains the version, a serial number, the algorithm used to sign it, the issuer's name, the validity period, the subject's name, the subject's public key, and extensions such as the subject alternative name and basic constraints. All of those together form the TBSCertificate, and it is exactly those bytes that are hashed and signed; the signature algorithm identifier and the signature value are appended outside them, which is why a verifier hashes the TBSCertificate and not the whole file.

2. Why did the TLS session succeed while the certificate check failed? Because the handshake only needs the server to prove it holds the private key matching the certificate it presented, and to agree session keys; that succeeded, so the traffic is encrypted. The certificate check asks a different question, whether the name in the certificate is vouched for by an authority the client already trusts, and a self-signed certificate is signed by its own key, so no chain leads to any trusted root and the client reports return code 18.

3. List the checks a client makes on a server certificate. That the name requested appears in the certificate, normally in the subject alternative name; that the current time lies between the notBefore and notAfter dates; that the signature on the certificate is intact, verified with the issuer's public key; that a chain can be built from the certificate through any intermediates to a root certificate the client already trusts, with each link's signature verified and each intermediate permitted to act as a CA; and that the certificate has not been revoked.

4. How would you make a laboratory setup in which the client accepts the certificate without warnings? By creating a small certification authority: generate a CA key and a self-signed CA certificate, then generate a key and a certificate signing request for the server, sign that request with the CA key, and configure the server with the resulting certificate. The CA certificate is then installed in the trust store of each client machine. The chain from the server certificate to the CA certificate can be followed, the CA is trusted because it was installed deliberately, and the client accepts it.

munotes.in655

Practical: Certificates and a Secure Session, End to End

5. What is the danger of teaching students to click through certificate warnings? The warning is the only check that distinguishes the real server from an impostor presenting its own certificate. A user trained to click through it will do so when an attacker is in the middle, so the attacker's certificate is accepted, the session is established with the attacker, and everything sent, including passwords, is read and forwarded. The encryption remains perfect and entirely useless.

Contents This chapter on its own page

munotes.in656

Chapter Ninety-Nine

Practical: Setting Up an Intrusion Detection System

Syllabus topic Practical 5, "Set up and configure an intrusion detection system"

In one line

Setting up an intrusion detection system is three quarters tuning: installing it and writing a rule that fires is the easy part, and a rule that fires on everything is worse than no rule at all.

Aim

To install an intrusion detection system, write a rule of your own, trigger it deliberately, read the alert it produces, and then tune the rule so that it reports the attack and not the ordinary traffic around it.

What is needed

  • A machine running Snort 3 (snort -V should print a version), or any IDS that reads similar rules.
  • A second machine, or the same one, to generate the traffic.
  • A file for your own rules: Snort reserves rule numbers below 1,000,000, so local rules start at sid 1000001.

Theory in four lines

  • A signature-based IDS compares traffic with patterns of known attacks; an anomaly-based one compares it with a profile of normal behaviour.
  • A Snort rule is a header, action, protocol, source address and port, direction, destination address and port, and options in brackets.
  • A rule that matches too much produces false alarms, and the base-rate arithmetic says that on a busy network even a small error rate buries the real alerts.
  • Therefore the finished product of this practical is not the rule but the tuned rule, with evidence of the tuning.

Procedure

  1. Install the IDS and confirm it runs and can see the traffic.
  2. Write one rule in the local rules file, with your own sid above 1,000,000 and a clear message.
  3. Generate traffic that must trigger it, and read the alert.
  4. Generate ordinary traffic and count how often the rule fires on that too.
  5. Tune: add conditions that describe the attack and not the ordinary case, and measure again.
  6. Record the rule before and after, the alerts, and the counts, in the journal.

The commands

# check the installation
snort -V

# a rule of your own, in a local rules file
# local.rules:
alert tcp $EXTERNAL_NET any -> $HOME_NET 80 (msg:"Somebody used the web server"; \
    sid:1000001; rev:1;)

# read a saved capture with just this rule file, printing alerts to the screen
snort -c /usr/local/etc/snort/snort.lua -R local.rules -r morning.pcap -A alert_fast

# or watch an interface live
snort -c /usr/local/etc/snort/snort.lua -R local.rules -i eth0 -A alert_fast

Use a saved capture while tuning. The same traffic each time is the only way to tell whether a change to the rule improved anything.

The program: the same morning, twice

The listing reads the same rule syntax and applies it to one morning's traffic: forty ordinary requests, six password attempts from a single address, and one student who mistyped a password once.

# Practical 5, exercise 8: a rule, an alert, and then the tuning. The engine below is a small
# stand-in for Snort that reads the same rule syntax, so that the effect of each change can be
# measured. Nothing is captured from a network: the traffic is a list built in the program.
import ipaddress, re

HOME = ipaddress.ip_network('10.21.0.0/16')

def parse(rule):
    """A Snort rule is a header of five parts and, in brackets, its options."""
    head, _, body = rule.partition(' (')
    action, proto, src, sport, arrow, dst, dport = head.split()
    options = dict(re.findall(r'(\w+):((?:"[^"]*"|[^;])*);', body))
    return dict(action=action, proto=proto, src=src, sport=sport, dst=dst, dport=dport, **options)

def address_ok(spec, ip):
    if spec == 'any':
        return True
    inside = ipaddress.ip_address(ip) in HOME
    if spec == '$HOME_NET':
        return inside
    if spec == '$EXTERNAL_NET':
        return not inside
    return ipaddress.ip_address(ip) in ipaddress.ip_network(spec)

def port_ok(spec, port):
    return spec == 'any' or int(spec) == port

def fires(rule, packet, seen):
    if rule['proto'] != packet['proto']:
        return False
    if not (address_ok(rule['src'], packet['src']) and address_ok(rule['dst'], packet['dst'])):
        return False
    if not (port_ok(rule['sport'], packet['sport']) and port_ok(rule['dport'], packet['dport'])):
        return False
    if 'content' in rule:
        text, *modifiers = rule['content'].split(',')
        text = text.strip('"')
        payload = packet['payload']
        if 'nocase' in [m.strip() for m in modifiers]:
            text, payload = text.lower(), payload.lower()
        if text not in payload:
            return False
    if 'flow' in rule and rule['flow'].strip() not in packet['flow']:
        return False
    if 'detection_filter' in rule:                 # Snort's own option for exactly this
        count = int(re.search(r'count (\d+)', rule['detection_filter']).group(1))
        seen[packet['src']] = seen.get(packet['src'], 0) + 1
        if seen[packet['src']] < count:
            return False
    return True

def run(rules, traffic):
    alerts, seen = [], {}
    for packet in traffic:
        for rule in rules:
            if fires(rule, packet, seen):
                alerts.append(('[1:%s:%s] %s' % (rule['sid'], rule['rev'],
                                                 rule['msg'].strip('"')), packet))
                break
    return alerts

# ---- the traffic of one morning: mostly ordinary, with two real attempts ----------------------
TRAFFIC = []
for n in range(40):                                # ordinary visitors reading the site
    TRAFFIC.append(dict(proto='tcp', src='203.0.113.%d' % (10 + n % 12), sport=40000 + n,
                        dst='10.21.50.8', dport=80, flow='to_server established',
                        payload='GET /notices/%d HTTP/1.1' % n, attack=False))
for n in range(6):                                 # one machine trying passwords on the portal
    TRAFFIC.append(dict(proto='tcp', src='198.51.100.9', sport=50000 + n, dst='10.21.50.8',
                        dport=80, flow='to_server established',
                        payload='POST /login user=admin&pass=guess%d' % n, attack=True))
TRAFFIC.append(dict(proto='tcp', src='203.0.113.4', sport=41000, dst='10.21.50.8', dport=80,
                    flow='to_server established',
                    payload='POST /login user=asha&pass=hers', attack=False))

FIRST = ['alert tcp $EXTERNAL_NET any -> $HOME_NET 80 '
         '(msg:"Somebody used the web server"; sid:1000001; rev:1;)']
TUNED = ['alert tcp $EXTERNAL_NET any -> $HOME_NET 80 '
         '(msg:"Repeated login attempts from one address"; flow:to_server; '
         'content:"POST /login",nocase; detection_filter:track by_src, count 5, seconds 60; '
         'sid:1000002; rev:1;)']

for title, rules in (('1. THE FIRST RULE, WRITTEN THE OBVIOUS WAY', FIRST),
                     ('2. THE SAME MORNING, WITH THE RULE TUNED', TUNED)):
    parsed = [parse(r) for r in rules]
    alerts = run(parsed, TRAFFIC)
    real = sum(1 for _, packet in alerts if packet['attack'])
    print(title)
    print('   rule: %s' % parsed[0]['msg'].strip('"'))
    print('   packets seen %d, alerts raised %d, of which worth reading %d'
          % (len(TRAFFIC), len(alerts), real))
    if alerts:
        print('   the first alert line: %s' % alerts[0][0])
        print('   from %s, %s' % (alerts[0][1]['src'], alerts[0][1]['payload']))
    share = 100 * real / len(alerts) if alerts else 0
    print('   %.0f%% of the alerts are worth reading' % share)
    print()

attempts = sum(1 for p in TRAFFIC if p['attack'])
print('3. WHAT THE TUNING DID')
print('   the attack was %d password attempts from one address in one minute' % attempts)
print('   the first rule reported every visit to the web server, %d of them'
      % len(run([parse(FIRST[0])], TRAFFIC)))
print('   the tuned rule waits for the fifth attempt from the same address, so an')
print('   ordinary student mistyping a password once does not appear at all')
munotes.in657

Practical: Setting Up an Intrusion Detection System

1. THE FIRST RULE, WRITTEN THE OBVIOUS WAY
   rule: Somebody used the web server
   packets seen 47, alerts raised 47, of which worth reading 6
   the first alert line: [1:1000001:1] Somebody used the web server
   from 203.0.113.10, GET /notices/0 HTTP/1.1
   13% of the alerts are worth reading

2. THE SAME MORNING, WITH THE RULE TUNED
   rule: Repeated login attempts from one address
   packets seen 47, alerts raised 2, of which worth reading 2
   the first alert line: [1:1000002:1] Repeated login attempts from one address
   from 198.51.100.9, POST /login user=admin&pass=guess4
   100% of the alerts are worth reading

3. WHAT THE TUNING DID
   the attack was 6 password attempts from one address in one minute
   the first rule reported every visit to the web server, 47 of them
   the tuned rule waits for the fifth attempt from the same address, so an
   ordinary student mistyping a password once does not appear at all
munotes.in658

Practical: Setting Up an Intrusion Detection System

What the output proves

The first rule works, and is useless. It fires on every packet it was written to describe: 47 alerts. Six of them concern the attack. An operator who reads 47 alert lines each morning to find six will stop reading them within a week, and the six will be missed.

The tuned rule describes the attack rather than the service. Three additions do it: flow:to_server so that only requests count; content:"POST /login",nocase so that reading pages does not match; and detection_filter: track by_src, count 5, seconds 60 so that a single mistyped password is ignored and only repeated attempts from one address raise anything.

The measurement is the deliverable. 13 per cent of alerts worth reading became 100 per cent, on the same traffic. Without the before-and-after counts, "I tuned the rule" is an assertion.

The student who mistyped once never appears. That is what the threshold is for, and it is also the part to explain in the viva: the rule now deliberately ignores the first four attempts, which is a decision with a cost, and the cost is that four attempts from an attacker also pass unreported.

munotes.in659

Practical: Setting Up an Intrusion Detection System

Questions the examiner asks

Why must local rules use sid 1000001 upwards? Because Snort reserves the numbers below 1,000,000 for the rules that ship with it, so a local rule numbered in that range collides with one of theirs.

What does rev mean? The revision of the rule. When you change a rule, raise it, so that an alert can be traced to the exact version that produced it.

Where would you place the sensor? Where it can see the traffic you care about: inside the firewall to see what got through, or outside to see what was attempted; on a mirror port or a network tap, not on a switch port that sees only its own traffic.

Does an IDS stop the attack? No. It reports. An intrusion prevention system sits in the path and can drop the traffic, at the risk that a false alarm now blocks legitimate work.

How would you detect the same attack without a signature? By profile: the number of failed logins per address per minute is a metric with a normal range, and this one lies far outside it, which is the statistical anomaly detection of the earlier chapters.

Common mistakes in the laboratory

  • Writing the rule and stopping. Untuned, it is noise.
  • Testing only that it fires. Also test that it does not fire on ordinary traffic, which is the harder half.
  • Tuning on live traffic, so that no two measurements are comparable. Use a saved capture.
  • Putting the sensor where it cannot see the traffic, then concluding that the rule is wrong.
  • Using a sid below 1,000,000, or never changing rev.
  • Leaving the threshold unexplained. A rule that ignores four attempts is a decision, and the journal should say why it was chosen.

Quick revision

  • Rule = header (action, protocol, source, direction, destination) + options in brackets.
  • Useful options: msg, content with modifiers such as nocase, flow, detection_filter, sid, rev.
  • Local sids start at 1000001; raise rev on every change.
  • Tune by describing the attack, not the service: direction, content, and a threshold.
  • Measure before and after on the same saved capture; the numbers are the deliverable.
  • An IDS reports; an IPS blocks, and a false alarm then costs service.

Test yourself

1. Write a Snort rule that alerts on repeated login attempts, and explain each part. alert tcp $EXTERNAL_NET any -> $HOME_NET 80 (msg:"Repeated login attempts from one address"; flow:to_server; content:"POST /login",nocase; detection_filter:track by_src, count 5, seconds 60; sid:1000002; rev:1;). The header says: raise an alert, for TCP, from any port on an outside address, to port 80 on an inside address. The options say: print that message; consider only traffic towards the server; fire only if the payload contains "POST /login" in any case; and only after five such packets from the same source address within sixty seconds; the rule is local number 1000002, revision 1.

munotes.in660

Practical: Setting Up an Intrusion Detection System

2. Why is a rule that fires on all traffic to a service worse than no rule? Because it produces an alert for every ordinary use of that service, so the small number of alerts that matter are buried among them. In the worked example 47 alerts contained 6 worth reading. An operator facing that ratio stops reading the alerts, so the system that was installed to detect intrusions now detects nothing, while giving the impression that it is working.

3. How do you demonstrate that tuning has improved a rule? By replaying the same saved capture through the rule before and after the change and counting, in each case, how many alerts were raised and how many of them corresponded to the activity you intended to catch. The pair of counts is the evidence. Tuning against live traffic proves nothing, because the traffic differs between runs.

4. What is the cost of a detection filter that waits for five attempts? That the first four attempts from any source are not reported, so an attacker who needs only a few guesses, or who spreads attempts across many source addresses, passes unnoticed. The benefit is that ordinary users who mistype a password once or twice never generate an alert. The threshold is a deliberate trade between the alerts an operator will actually read and the attacks that will be missed, and it should be recorded with its reason.

5. Where should an IDS sensor be placed, and why does it matter? Where it can see the traffic of interest: on a mirror port, a network tap or an inline position carrying the traffic between the protected network and the outside. Inside the firewall it reports what got through, which is what an operator must act on; outside it reports everything attempted, which is noisier but shows what is being tried. A sensor on an ordinary switch port sees only its own traffic and will report nothing however good the rules are.

Contents This chapter on its own page

munotes.in661

Chapter One Hundred

Practical: Analysing a Malware Sample, Safely

Syllabus topic Practical 5, "Analyze and identify malware samples using antivirus tools"

In one line

Analysing a suspicious file means learning what it is without letting it run anywhere it can do harm: identify it, hash it, read what is readable, and only then, in a machine that can be thrown away, watch what it does.

The safety rules

These come before the technique, and they are not optional.

  1. Never on a college machine, a laboratory machine, or your own. Use a virtual machine created for this purpose, whose disk can be deleted afterwards.
  2. Take a snapshot before you start, and restore it afterwards. The snapshot, not a cleaning tool, is how the machine is made clean again.
  3. No network. Disconnect the virtual machine's adapter, or attach it to a host-only network with no route out. A specimen that reaches the Internet can attack somebody else from your college's address, and that is your college's problem and yours.
  4. No shared folders, no clipboard sharing, no USB pass-through. These are the paths by which a specimen leaves a virtual machine.
  5. Use a test file, not live malware. The EICAR test file is a small, harmless file that the antivirus industry agreed all products would detect, made exactly so that detection can be tested without a real specimen. It is published by EICAR and can be downloaded from eicar.org. It is not reproduced in this book, because a page containing it would be quarantined on your own device: that is the point of it.
  6. Do not collect real malware, do not accept it from anyone, and do not keep it. Possessing and running malicious code on systems that are not yours engages sections 43 and 66 of the IT Act.
  7. Static before dynamic. Everything that can be learned without running the file is learned first.
  8. If something unexpected happens, stop, restore the snapshot, and tell the laboratory in charge. Under CERT-In's directions, some incidents must be reported within six hours, and that duty belongs to the institution.

Aim

To examine a suspicious file with antivirus tools and by static analysis, to identify it and describe what it appears to do, without allowing it to affect any system other than a disposable one.

What is needed

  • A virtual machine with a snapshot and no network.
  • An antivirus product on it, updated before the adapter is disconnected.
  • The EICAR test file for checking that detection works.
  • Standard tools: something to show a file's type, its hash, and its printable strings.

Theory in four lines

  • Static analysis examines the file without executing it: type, size, hashes, strings, structure, entropy.
  • Dynamic analysis runs it under observation and records what it does: files, registry, processes, network.
  • A hash identifies the specimen exactly and is what you share; the file is what you do not share.
  • Packed malware hides its strings until it unpacks itself in memory, which is why static analysis alone often ends in "it is packed", and that is a finding, not a failure.
munotes.in662

Practical: Analysing a Malware Sample, Safely

Procedure

  1. Prepare the virtual machine, install and update the antivirus, take a snapshot, disconnect the network.
  2. Check that detection works by putting the EICAR test file on the machine: the product should alert. If it does not, nothing that follows can be trusted.
  3. Copy the specimen in, and do not open it.
  4. Static analysis: identify the type from the first bytes; compute MD5, SHA-1 and SHA-256; extract printable strings and note the interesting ones; measure entropy to see whether parts are packed.
  5. Scan it with the antivirus and record the name it gives.
  6. Only if the exercise requires it, and with the network still disconnected, run it and record the changes; then restore the snapshot.
  7. Write the report: what it is, what it appears to do, what the antivirus called it, and the hashes.

The program: the static half, done properly

The listing builds an inert specimen and then does what a student does to a real one: identifies it, hashes it, reads its strings, and measures how compressed each part looks. It runs nothing.

# Practical 5, exercise 9: the static half of examining a suspicious file. Every step here is
# done WITHOUT RUNNING ANYTHING: identify, hash, read the printable strings, measure how
# compressed each part looks. The file examined is built below and is inert by construction.
import hashlib, math, random

# ---- a stand-in specimen: a small program-like file with a packed-looking tail ----------------
rng = random.Random(100)
header = b'MZ\x90\x00\x03\x00\x00\x00\x04\x00\x00\x00\xff\xff\x00\x00'
readable = (b'This program cannot be run in DOS mode.\x00'
            b'KERNEL32.dll\x00CreateFileW\x00WriteFile\x00InternetOpenUrlA\x00'
            b'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\x00'
            b'http://updates.example.invalid/collect\x00')
packed = bytes(rng.randrange(256) for _ in range(2048))      # looks compressed or encrypted
sample = header + readable + bytes(64) + packed
open('sample.bin', 'wb').write(sample)

# ---- 1. identify it, without opening it in anything that would run it -------------------------
MAGIC = {b'MZ': 'a Windows executable', b'\x7fELF': 'a Linux executable',
         b'PK\x03\x04': 'a zip archive, which may hold anything',
         b'%PDF': 'a PDF document'}
kind = next((what for magic, what in MAGIC.items() if sample.startswith(magic)), 'unknown')
print('1. WHAT IS IT')
print('   %d bytes, first bytes %s: %s' % (len(sample), sample[:4].hex(), kind))

# ---- 2. hash it: the one thing to share, and the one thing to look up -------------------------
digests = {name: hashlib.new(name, sample).hexdigest()
           for name in ('md5', 'sha1', 'sha256')}
for name, value in digests.items():
    print('   %-7s %s' % (name, value))
KNOWN_GOOD = {'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'}
print('   on the list of files known to be safe: %s' % (digests['sha256'] in KNOWN_GOOD))

# ---- 3. the printable strings, which is where intentions leak ---------------------------------
def strings(data, least=6):
    out, run = [], bytearray()
    for byte in data:
        if 32 <= byte < 127:
            run.append(byte)
        else:
            if len(run) >= least:
                out.append(run.decode())
            run = bytearray()
    if len(run) >= least:
        out.append(run.decode())
    return out

INTERESTING = ('http://', 'https://', '.dll', 'CurrentVersion\\Run', 'WriteFile', 'Internet')
found = strings(sample)
print()
print('2. WHAT THE PRINTABLE STRINGS SAY (%d of them, %d worth a second look)'
      % (len(found), sum(any(k in s for k in INTERESTING) for s in found)))
for text in found:
    marks = [k for k in INTERESTING if k in text]
    if marks:
        print('   %-52s %s' % (text[:52], 'note: ' + ', '.join(marks)))

# ---- 4. entropy: a packed or encrypted region looks like noise --------------------------------
def entropy(block):
    if not block:
        return 0.0
    counts = [0] * 256
    for byte in block:
        counts[byte] += 1
    return -sum((c / len(block)) * math.log2(c / len(block)) for c in counts if c)

print()
print('3. HOW COMPRESSED EACH PART LOOKS (8.0 bits a byte is indistinguishable from noise)')
for name, block in (('the header', sample[:16]), ('the readable part', readable),
                    ('the tail', packed), ('the whole file', sample)):
    print('   %-20s %5d bytes  %.2f bits a byte' % (name, len(block), entropy(block)))
print('   a tail that close to 8.0 is packed, compressed or encrypted: the strings in it,')
print('   and what it does, are not visible until it unpacks itself, which needs the')
print('   dynamic half of the practical, inside an isolated machine')

# ---- 5. what may be shared, and what may not --------------------------------------------------
print()
print('4. WHAT GOES IN THE REPORT')
print('   share: the SHA-256 hash, the file size, the strings, the entropy figures')
print('   never share: the file itself outside the isolated machine, or anything')
print('   it may have read from the computer it came from')
munotes.in663

Practical: Analysing a Malware Sample, Safely

1. WHAT IS IT
   2305 bytes, first bytes 4d5a9000: a Windows executable
   md5     75a0a2b56a75c60958b44b47d8164341
   sha1    079033bce46305c9c6ca29fb4284c8adf1d3b3a6
   sha256  d6b2a2a62f1eee09d0df51b541cbda2407972b7bbe8fb90be83e28dc867b24a4
   on the list of files known to be safe: False

2. WHAT THE PRINTABLE STRINGS SAY (9 of them, 5 worth a second look)
   KERNEL32.dll                                         note: .dll
   WriteFile                                            note: WriteFile
   InternetOpenUrlA                                     note: Internet
   SOFTWARE\Microsoft\Windows\CurrentVersion\Run        note: CurrentVersion\Run
   http://updates.example.invalid/collect               note: http://

3. HOW COMPRESSED EACH PART LOOKS (8.0 bits a byte is indistinguishable from noise)
   the header              16 bytes  2.09 bits a byte
   the readable part      177 bytes  5.00 bits a byte
   the tail              2048 bytes  7.91 bits a byte
   the whole file        2305 bytes  7.82 bits a byte
   a tail that close to 8.0 is packed, compressed or encrypted: the strings in it,
   and what it does, are not visible until it unpacks itself, which needs the
   dynamic half of the practical, inside an isolated machine

4. WHAT GOES IN THE REPORT
   share: the SHA-256 hash, the file size, the strings, the entropy figures
   never share: the file itself outside the isolated machine, or anything
   it may have read from the computer it came from
munotes.in664

Practical: Analysing a Malware Sample, Safely

What the output proves

A file announces its type in its first bytes. 4d 5a is MZ, the mark of a Windows executable, whatever the file is named. The name on disk is the least reliable thing about a specimen.

Three hashes, three purposes. SHA-256 is what you record and share, because it identifies the specimen without passing it on. MD5 and SHA-1 appear because many older databases are indexed by them; neither should be relied on to prove that two different files are the same.

The strings are where intention leaks. Five of nine are worth a second look: a call to write files, a call to fetch a URL, a registry key that makes a program run at every startup, and a web address. None of that proves anything on its own, and together it describes a program that wants to persist and to talk to somewhere. That is a hypothesis, to be written as a hypothesis.

Entropy tells you what you cannot see. The readable part measures 5.00 bits a byte; the tail measures 7.91, which is indistinguishable from random. That is what packing, compression or encryption looks like, and it means the strings in that part cannot be read until the file unpacks itself. The right conclusion is "it is packed", not "there is nothing there".

What leaves the machine is a description, not a specimen. Hashes, sizes, strings and findings; never the file, and never anything it may have collected.

Questions the examiner asks

Why is static analysis done first? Because it is free of risk and often answers the question. Running a specimen is the step that can go wrong, so it comes last and only when needed.

What does high entropy mean? That the bytes are close to random, which is what compressed, packed or encrypted data looks like. It suggests the file will unpack itself when run, and it is why the strings are missing.

Why use EICAR rather than real malware? Because it lets you prove that the antivirus is working, and that the procedure is right, at no risk. It is a test file agreed by the industry, not a specimen.

What would you look for in dynamic analysis? Files created or changed; registry keys added, in particular those that cause a program to run at startup; new processes and services; and attempted network connections, which is why the adapter must be disconnected.

May you analyse malware on your own laptop? No. The rules above exist because the failure is not recoverable and may not be confined to your machine.

munotes.in665

Practical: Analysing a Malware Sample, Safely

Common mistakes in the laboratory

  • Opening the file to "see what it is". The identification is done from the bytes, not by opening it.
  • Leaving the network connected because the analysis tool wants to look something up.
  • Analysing on the host and not in the virtual machine.
  • Reporting "no strings found" when the answer is that the file is packed.
  • Trusting the file's extension or icon, both of which are chosen by whoever made it.
  • Sharing the sample with classmates, which multiplies the risk for no benefit; share the hash.
  • Skipping the snapshot, so there is no way back.

Quick revision

  • Rules: disposable virtual machine, snapshot, no network, no shared folders, EICAR not live malware, static before dynamic, stop and report if surprised.
  • Static: type from the first bytes, hashes (share SHA-256), strings, entropy, antivirus name.
  • Dynamic: files, registry, processes, network, then restore the snapshot.
  • Entropy near 8.0 bits a byte means packed, compressed or encrypted.
  • Share the hash and the findings, never the file.
  • India: incidents reported to CERT-In within six hours; unauthorised access and damage are IT Act sections 43 and 66.

Test yourself

1. State the safety rules for examining a suspicious file, and why each exists. Work only in a virtual machine created for the purpose, with a snapshot taken beforehand, because the snapshot is the only reliable way to restore it. Disconnect the network, because a specimen that reaches the Internet can attack others from your address. Disable shared folders, clipboard sharing and USB pass-through, because those are the routes out of a virtual machine. Use the EICAR test file rather than live malware, because it proves the antivirus works at no risk. Do static analysis before dynamic, because it carries no risk and often suffices. Stop, restore and report anything unexpected, because incident reporting is a legal duty of the institution.

2. What is the EICAR file and what is it for? It is a short, harmless file that the antivirus industry agreed every product would detect as though it were malware. It exists so that people can test whether antivirus software is installed, working and able to alert, without needing a real specimen. Because every product detects it, it cannot safely be reproduced in a document, and it is downloaded from EICAR when needed.

3. Distinguish static from dynamic analysis, and give three techniques of each. Static analysis examines the file without executing it: identifying its type from its first bytes, computing its hashes, extracting its printable strings, examining its structure and measuring its entropy. Dynamic analysis runs it under observation in an isolated machine and records its effects: the files it creates or changes, the registry keys it adds, particularly those that cause it to run at startup, the processes it starts and the network connections it attempts. Static analysis carries no risk and is always done first.

munotes.in666

Practical: Analysing a Malware Sample, Safely

4. You find that most of a file is at nearly 8 bits of entropy per byte. What do you conclude, and what do you do next? That this part is packed, compressed or encrypted, so its code and strings are not visible in the file and will only appear when the program unpacks itself in memory. This is a finding to be reported, not a failure of the analysis. The next steps are to record the entropy figures and what little is readable outside that region, scan with the antivirus, which may recognise the packer or the family, and, if the exercise permits and the machine is properly isolated, proceed to dynamic analysis, where the unpacked form can be observed.

5. What may you share from a malware analysis, and what may you not? You may share the description: the file's size and type, its SHA-256 hash, the strings that were readable, the entropy measurements, the name the antivirus gave it, and the behaviour observed. You may not share the file itself outside the isolated environment, because doing so spreads it, and you may not share anything it may have collected from the machine it came from, which may include other people's personal data and is subject to the same handling rules as any other personal information.

Contents This chapter on its own page

munotes.in667

Chapter One Hundred One

How the Paper Is Set, and How to Answer It

Syllabus topic Module 2, the examination itself

In one line

Thirty marks in sixty minutes, six answers of five marks each, chosen two from four in each of three questions, where the third question deliberately crosses both modules: which means short, complete, well-labelled answers, and breadth of preparation rather than depth in a few favourites.

The pattern

TypeTheory. 2 credits, 30 hours, 50 marks in all
ExternalSemester End Theory Examination, 1 hour, 30 marks
Q.1on Module 1, answer any 2 of 4, 10 marks
Q.2on Module 2, answer any 2 of 4, 10 marks
Q.3on Modules 1 and 2 together, answer any 2 of 4, 10 marks
Internal20 marks: Class Test 1 on Module 1 for 10 and Class Test 2 on Module 2 for 10, averaged to 10; an assignment on each module for 5 each, 10 in all

Q.3 is the one students are least ready for, because it is drawn from both modules together, which means comparisons: symmetric against public-key, a MAC against a digital signature, IPsec against SSL. The next and last chapter of this book exists for that question alone.

The run: what an hour buys

# The paper, in numbers: what one hour buys, and what "any two of four" is worth to a student
# who has prepared part of the syllabus. The pattern is MU's own; the arithmetic is here.
from math import comb

MINUTES, EXTERNAL = 60, 30
QUESTIONS, CHOOSE, OF = 3, 2, 4
PER_ANSWER = EXTERNAL // (QUESTIONS * CHOOSE)

print('1. THE PAPER')
print('   %d marks in %d minutes: %d questions, any %d of %d in each, %d marks an answer'
      % (EXTERNAL, MINUTES, QUESTIONS, CHOOSE, OF, PER_ANSWER))
print('   Q.1 Module 1     Q.2 Module 2     Q.3 both modules together')
print('   answers to write: %d' % (QUESTIONS * CHOOSE))
print('   minutes an answer, if none are spent choosing: %.1f'
      % (MINUTES / (QUESTIONS * CHOOSE)))
for reading in (5, 8):
    left = MINUTES - reading
    print('   after %d minutes reading the paper and choosing: %.1f minutes an answer'
          % (reading, left / (QUESTIONS * CHOOSE)))
print('   at a considered 20 words a minute, that is about %d words: a page and a half'
      % round((MINUTES - 8) / (QUESTIONS * CHOOSE) * 20, -1))

# ---- 2. the internal 20 --------------------------------------------------------------------
print()
print('2. THE INTERNAL 20')
tests = [('Class Test 1, on Module 1', 10), ('Class Test 2, on Module 2', 10)]
average = sum(m for _, m in tests) / len(tests)
assignments = [('an assignment on Module 1', 5), ('an assignment on Module 2', 5)]
print('   %s and %s, averaged to %.0f' % (tests[0][0], tests[1][0], average))
print('   %s and %s, %d in all' % (assignments[0][0], assignments[1][0],
                                   sum(m for _, m in assignments)))
print('   internal total %.0f, external %d, paper total %.0f'
      % (average + sum(m for _, m in assignments), EXTERNAL,
         average + sum(m for _, m in assignments) + EXTERNAL))

# ---- 3. what "any two of four" is worth -----------------------------------------------------
def at_least_two_of_four(p):
    """If each of the four parts is one you can answer with probability p, and they are
    independent, this is the chance that at least two of them are. An idealisation: real
    papers are not drawn at random. It still shows the shape."""
    return sum(comb(OF, k) * p ** k * (1 - p) ** (OF - k) for k in range(CHOOSE, OF + 1))

print()
print('3. WHY BREADTH BEATS DEPTH IN THIS PATTERN')
print('   share of the   can answer 2 of 4    can do it in all')
print('   syllabus you   in one question      three questions')
print('   have prepared')
for prepared in (0.4, 0.5, 0.6, 0.7, 0.8, 0.9):
    one = at_least_two_of_four(prepared)
    print('   %13.0f%% %16.1f%% %16.1f%%'
          % (prepared * 100, one * 100, one ** QUESTIONS * 100))
print('   the third column falls much faster than the first, because all three questions')
print('   must be attempted. Preparing 90 per cent of the syllabus is not 1.8 times as')
print('   good as preparing 50 per cent of it: it is about three times as good.')

# ---- 4. eight minutes, spent -----------------------------------------------------------------
print()
print('4. HOW TO SPEND THE EIGHT MINUTES OF ONE ANSWER')
PLAN = [('write the definition', 1), ('name the parts, numbered', 2),
        ('draw the small figure', 2), ('give the distinction or example', 2),
        ('one closing line', 1)]
spent = 0
for what, minutes in PLAN:
    spent += minutes
    print('   %-32s %d minute(s), %d gone' % (what, minutes, spent))
print('   the marks are in the NAMED PARTS and the FIGURE, not in the opening sentence')
munotes.in668

How the Paper Is Set, and How to Answer It

1. THE PAPER
   30 marks in 60 minutes: 3 questions, any 2 of 4 in each, 5 marks an answer
   Q.1 Module 1     Q.2 Module 2     Q.3 both modules together
   answers to write: 6
   minutes an answer, if none are spent choosing: 10.0
   after 5 minutes reading the paper and choosing: 9.2 minutes an answer
   after 8 minutes reading the paper and choosing: 8.7 minutes an answer
   at a considered 20 words a minute, that is about 170 words: a page and a half

2. THE INTERNAL 20
   Class Test 1, on Module 1 and Class Test 2, on Module 2, averaged to 10
   an assignment on Module 1 and an assignment on Module 2, 10 in all
   internal total 20, external 30, paper total 50

3. WHY BREADTH BEATS DEPTH IN THIS PATTERN
   share of the   can answer 2 of 4    can do it in all
   syllabus you   in one question      three questions
   have prepared
              40%             52.5%             14.5%
              50%             68.8%             32.5%
              60%             82.1%             55.3%
              70%             91.6%             76.9%
              80%             97.3%             92.1%
              90%             99.6%             98.9%
   the third column falls much faster than the first, because all three questions
   must be attempted. Preparing 90 per cent of the syllabus is not 1.8 times as
   good as preparing 50 per cent of it: it is about three times as good.

4. HOW TO SPEND THE EIGHT MINUTES OF ONE ANSWER
   write the definition             1 minute(s), 1 gone
   name the parts, numbered         2 minute(s), 3 gone
   draw the small figure            2 minute(s), 5 gone
   give the distinction or example  2 minute(s), 7 gone
   one closing line                 1 minute(s), 8 gone
   the marks are in the NAMED PARTS and the FIGURE, not in the opening sentence
munotes.in669

How the Paper Is Set, and How to Answer It

What the run establishes, in order.

Under nine minutes an answer. Six answers in sixty minutes is ten minutes each, and reading the paper and choosing takes the first five to eight. At a considered twenty words a minute that is about 170 words: a page and a half in an ordinary hand. An answer three pages long is not a better answer; it is two answers' worth of time spent on one.

The internal is not an afterthought. Two class tests averaged to ten, plus two assignments of five, are 20 of the 50 marks, and they are the marks over which a student has the most control.

Breadth beats depth, and the numbers are stark. Preparing half the syllabus gives about a 69 per cent chance of finding two answerable parts in one question, but only about 33 per cent of managing it in all three. Preparing 80 per cent gives 92 per cent. The reason is that all three questions must be attempted, so the per-question chance is multiplied by itself three times.

Eight minutes has a shape. A definition, the named parts numbered, a small figure, a distinction or example, and one closing line. The marks are in the named parts and the figure.

What a 5-mark answer looks like

Two examples, written to the length the arithmetic allows.

Q.2 (b) Explain the SYN flooding attack and two defences against it.

When a client opens a TCP connection it sends a SYN, and the server replies with a SYN-ACK and keeps state for the half-open connection until the handshake completes or times out. In a SYN flood the attacker sends many SYNs with source addresses that will never answer, so the entries remain until they expire; when the backlog of half-open connections is full, genuine connections are refused. The attack needs very little bandwidth: a backlog of 128 entries held for 120 seconds is kept full by about one packet a second.

munotes.in670

How the Paper Is Set, and How to Answer It

Two defences. SYN cookies: the server keeps no state at all on receiving a SYN, encoding what it needs into the sequence number it sends and recovering it from the client's reply, so there is no table left to exhaust. Reducing the SYN-RECEIVED timer, or recycling the oldest half-open entry, which frees space sooner, at the cost of dropping some genuine clients on slow connections.

Q.1 (c) Distinguish a message authentication code from a digital signature.

Both prove that a message has not been altered, and both are computed over the whole message, but they differ in the key used and therefore in what they prove.

A MAC is computed with a secret key shared between sender and receiver, for example HMAC-SHA-256. Either party can compute it, so the receiver knows the message came from someone holding the key, but cannot prove to anyone else which of them sent it. It gives integrity and authentication, not non-repudiation.

A digital signature is computed with the sender's private key and checked with the matching public key. Only the holder of the private key can produce it, and anyone at all can check it, so it gives integrity, authentication and non-repudiation, and can be shown to a third party as evidence. A signature is larger and much slower to compute than a MAC, which is why MACs protect bulk traffic and signatures protect keys, certificates and documents.

How to answer, in seven rules

  1. Read all twelve parts first and choose six. Five minutes here saves more than it costs.
  2. Answer the question that was asked. "Distinguish" wants a comparison, not two descriptions.
  3. Define first, in one sentence, before anything else.
  4. Number the parts. Examiners award marks against named items, and a numbered list makes them findable.
  5. Draw the small figure where the topic has one: the IPsec modes, the firewall configurations, the malware map. It is quick and it is worth marks.
  6. Give one concrete example or figure, such as the 8.5-second doubling, the 166-day patch gap, or the six-hour reporting duty. Specifics separate a good answer from a vague one.
  7. Stop at a page and a half and go to the next answer. An unanswered part scores zero, and no answer scores more than five.

What loses marks

  • Writing everything you know about the topic instead of what was asked.
  • No definition, so the first mark is missing.
  • A wall of prose with no numbering and no figure.
  • Running out of time and leaving the sixth answer blank: that is five marks of the thirty.
  • Answering three parts of one question when only two are counted.
  • Vagueness: "very secure", "it is fast". Which algorithm, how many bits, what date.
munotes.in671

How the Paper Is Set, and How to Answer It

Quick revision

  • External: 1 hour, 30 marks; Q.1 Module 1, Q.2 Module 2, Q.3 both; any 2 of 4, 5 marks each.
  • Internal: 20 = two class tests averaged to 10, plus two assignments of 5.
  • About 8.7 minutes and 170 words an answer after choosing.
  • Shape: definition, numbered parts, figure, distinction or example, closing line.
  • Q.3 is comparisons across both modules: prepare them deliberately.
  • Prepare breadth: half the syllabus gives about a third chance of a full attempt; 80 per cent gives about 92 per cent.

Test yourself

1. State the pattern of the external examination. One hour, thirty marks, in three questions. Question 1 is set on Module 1, question 2 on Module 2, and question 3 on both modules together. Each question has four parts of which any two are to be answered, so six answers are written in all, each worth five marks.

2. How are the internal twenty marks made up? By two class tests, one on each module, each marked out of ten and the two averaged to give ten marks, together with one assignment on each module, each worth five marks, giving ten more: twenty in all, which with the thirty of the external examination makes the subject's fifty.

3. How long is an answer, and what should it contain? About eight to nine minutes of writing, which is roughly 170 words or a page and a half. It should open with a one-sentence definition, then give the parts of the answer as a numbered list, include the small figure if the topic has one, give a distinction or a concrete example with real figures, and close with a single sentence. Length beyond that takes time from another answer, which is worth five marks.

4. Why does the pattern reward breadth of preparation? Because all three questions must be attempted and each requires two answerable parts out of four. The chance of managing that in one question is high even with partial preparation, but it must be achieved three times over, so the chance of a full attempt is that figure multiplied by itself three times. Preparing half the syllabus gives roughly a two-in-three chance per question but only about one in three overall, while preparing 80 per cent gives about 92 per cent.

5. What kind of question is question 3, and how do you prepare for it? It is drawn from Modules 1 and 2 together, which in practice means comparisons that span the two: symmetric against public-key cryptography, a message authentication code against a digital signature, a hash against a MAC, IPsec against SSL or TLS, PGP against S/MIME, and a firewall against an intrusion detection system. You prepare by learning each as a table of differences with the one sentence that decides between them, which is the subject of the last chapter of this book.

Contents This chapter on its own page

munotes.in672

Chapter One Hundred Two

The Comparisons That Cross Both Modules

Syllabus topic Modules 1 and 2 together, which is what MU's Q.3 is drawn from

In one line

Six comparisons carry Q.3, and each comes down to one sentence: symmetric is fast and small but needs a shared key; a MAC proves integrity to a partner and a signature proves it to the world; a hash proves nothing about who; IPsec protects a path and TLS protects a connection; PGP trusts people and S/MIME trusts authorities; a firewall decides and an intrusion detection system reports.

The run: the four that can be measured

# The four comparisons that can be measured rather than asserted. Every number below is either
# computed here or taken from a named table, and nothing is sent anywhere.
import hashlib, hmac

# ---- 1. symmetric against public-key: the same strength costs very different keys -------------
# NIST SP 800-57 Part 1 Rev. 5, Table 2: comparable security strengths.
STRENGTHS = [(112, '3TDEA', 2048, '224 to 255'), (128, 'AES-128', 3072, '256 to 383'),
             (192, 'AES-192', 7680, '384 to 511'), (256, 'AES-256', 15360, '512 or more')]
print('1. SYMMETRIC AGAINST PUBLIC-KEY (SP 800-57 Part 1 Rev. 5, Table 2)')
print('   strength  symmetric   RSA      elliptic curve   RSA bits per bit of strength')
for bits, symmetric, rsa, ecc in STRENGTHS:
    print('   %-9s %-11s %-8s %-16s %d' % (bits, symmetric, rsa, ecc, rsa // bits))
print('   the deciding sentence: symmetric is far smaller and faster, so public-key is used')
print('   to agree a symmetric key and then gets out of the way')

# ---- 2. hash against MAC against signature: the same message, three tags ----------------------
MESSAGE = b'Transfer Rs 25,000 to account 41179 on 5 October 2026.'
TAMPERED = MESSAGE.replace(b'25,000', b'250,000')
SHARED = b'the key both ends hold'
plain_hash = hashlib.sha256(MESSAGE).digest()
mac = hmac.new(SHARED, MESSAGE, hashlib.sha256).digest()
print()
print('2. HASH, MAC AND SIGNATURE OVER THE SAME MESSAGE')
print('   %-22s %-6s %s' % ('', 'bytes', 'what an attacker who changes the message can do'))
print('   %-22s %-6d %s' % ('SHA-256 hash', len(plain_hash),
                            'recompute it: no key is needed'))
print('   %-22s %-6d %s' % ('HMAC-SHA-256', len(mac),
                            'nothing, without the shared key'))
print('   %-22s %-6d %s' % ('RSA-2048 signature', 256,
                            'nothing, without the private key'))
forged_hash = hashlib.sha256(TAMPERED).digest()
forged_mac = hmac.new(b'a guess at the key', TAMPERED, hashlib.sha256).digest()
print('   the changed message with a recomputed HASH passes a hash check:  %s'
      % (hashlib.sha256(TAMPERED).digest() == forged_hash))
print('   the changed message with a guessed MAC passes a MAC check:       %s'
      % hmac.compare_digest(forged_mac, hmac.new(SHARED, TAMPERED, hashlib.sha256).digest()))
print('   a hash alone proves nothing about WHO: it needs a key or a signature')

# ---- 3. what a MAC cannot do, and a signature can ---------------------------------------------
print()
print('3. WHO CAN PRODUCE THE TAG, AND WHO CAN CHECK IT')
ROWS = [('MAC', 'anyone with the shared key', 'anyone with the shared key', 'no'),
        ('signature', 'only the private-key holder', 'anyone at all', 'yes')]
print('   %-11s %-28s %-26s %s' % ('', 'can produce', 'can check', 'third party?'))
for name, produce, check, third in ROWS:
    print('   %-11s %-28s %-26s %s' % (name, produce, check, third))
print('   both ends can compute the same MAC, so neither can prove which of them made it')

# ---- 4. IPsec against TLS: what an observer on the path can still see --------------------------
FIELDS = ['the two addresses', 'the port, and so the service', 'the host name asked for',
          'the page path and the cookie', 'the data itself']
VISIBLE = {'nothing (plain)': [True, True, True, True, True],
           'TLS': [True, True, True, False, False],
           'IPsec, transport': [True, False, False, False, False],
           'IPsec, tunnel between gateways': [False, False, False, False, False]}
print()
print('4. WHAT AN OBSERVER BETWEEN THE TWO ENDS CAN STILL SEE')
print('   %-32s %s' % ('', '  '.join('%d' % (i + 1) for i in range(len(FIELDS)))))
for name, row in VISIBLE.items():
    print('   %-32s %s' % (name, '  '.join('y' if v else '.' for v in row)))
for i, field in enumerate(FIELDS, 1):
    print('   %d = %s' % (i, field))
print('   TLS protects one connection end to end; IPsec protects everything between two')
print('   points, and in tunnel mode hides which hosts are talking at all')
munotes.in673

The Comparisons That Cross Both Modules

1. SYMMETRIC AGAINST PUBLIC-KEY (SP 800-57 Part 1 Rev. 5, Table 2)
   strength  symmetric   RSA      elliptic curve   RSA bits per bit of strength
   112       3TDEA       2048     224 to 255       18
   128       AES-128     3072     256 to 383       24
   192       AES-192     7680     384 to 511       40
   256       AES-256     15360    512 or more      60
   the deciding sentence: symmetric is far smaller and faster, so public-key is used
   to agree a symmetric key and then gets out of the way

2. HASH, MAC AND SIGNATURE OVER THE SAME MESSAGE
                          bytes  what an attacker who changes the message can do
   SHA-256 hash           32     recompute it: no key is needed
   HMAC-SHA-256           32     nothing, without the shared key
   RSA-2048 signature     256    nothing, without the private key
   the changed message with a recomputed HASH passes a hash check:  True
   the changed message with a guessed MAC passes a MAC check:       False
   a hash alone proves nothing about WHO: it needs a key or a signature

3. WHO CAN PRODUCE THE TAG, AND WHO CAN CHECK IT
               can produce                  can check                  third party?
   MAC         anyone with the shared key   anyone with the shared key no
   signature   only the private-key holder  anyone at all              yes
   both ends can compute the same MAC, so neither can prove which of them made it

4. WHAT AN OBSERVER BETWEEN THE TWO ENDS CAN STILL SEE
                                    1  2  3  4  5
   nothing (plain)                  y  y  y  y  y
   TLS                              y  y  y  .  .
   IPsec, transport                 y  .  .  .  .
   IPsec, tunnel between gateways   .  .  .  .  .
   1 = the two addresses
   2 = the port, and so the service
   3 = the host name asked for
   4 = the page path and the cookie
   5 = the data itself
   TLS protects one connection end to end; IPsec protects everything between two
   points, and in tunnel mode hides which hosts are talking at all
munotes.in674

The Comparisons That Cross Both Modules

1. Symmetric against public-key cryptography

SymmetricPublic-key
Keysone shared secreta pair: public and private
Key for n people talking pairwisen(n-1)/2 keysn pairs
Speedfast: AES encrypts at gigabits a secondslow: thousands of operations a second
Key size for 128-bit strength128 bits3072 bits RSA, or 256 to 383 for elliptic curve
Solvesbulk confidentialitykey distribution, and signatures
ExamplesDES, 3DES, AES, RC4RSA, Diffie-Hellman, DSA, ECDSA

The sentence that decides it: public-key cryptography is used to agree or deliver a symmetric key and to sign; the symmetric key then does the work, because at the same security strength it is about twenty-four times smaller and enormously faster.

2. Message authentication code against digital signature

MACDigital signature
Key usedone shared secretsigner's private key
Checked withthe same shared secretthe signer's public key
Who can produce iteither partyonly the signer
Who can check iteither partyanyone
Non-repudiationnoyes
Size in the run32 bytes256 bytes
Speedvery fastslow
Typical useevery record of a TLS sessioncertificates, documents, software

The sentence that decides it: if the two parties only need to trust each other, a MAC is smaller and faster; if either may later need to prove to a third party who wrote the message, only a signature will do.

3. Hash against message authentication code

HashMAC
Keynonea shared secret
Anyone can compute ityes, which is the problemno
Detects accidental changeyesyes
Detects deliberate changeno: the attacker recomputes ityes
ExamplesSHA-256, SHA-3HMAC-SHA-256, CMAC

The sentence that decides it: a hash on its own protects against accidents, not against an attacker, because an attacker who changes the message simply computes the new hash; a MAC adds the key that the attacker does not have. The run shows exactly that.

4. IPsec against SSL or TLS

IPsecSSL/TLS
Layernetworktransport, above TCP
Protectsevery packet between two pointsone connection between two programs
Applications must changeno, it is transparentthe application must use it
Configured bynetwork administrators, both endsbuilt into browsers and servers
Hides the port and serviceyesno
Hides which hosts are talkingyes, in tunnel modeno
Typical usea VPN into a campus or office networkevery HTTPS site
munotes.in675

The Comparisons That Cross Both Modules

The sentence that decides it: IPsec protects a path and everything that crosses it, without the applications knowing; TLS protects one connection end to end and needs nothing from the network. The run's table shows what each still leaves visible.

5. PGP against S/MIME

PGPS/MIME
Trust modelthe web of trust: users sign each other's keysX.509 certificates from certification authorities
Who vouches for a keyyour friends, and their judgementa CA, and the chain to a root
Formatits own, with radix-64 armourCMS, carried as MIME parts
Strengthworks without any authority; good for individualsfits organisations, which already have a directory and a CA
Weaknesskey distribution is the user's problemtrusts the CA, and revocation is awkward
Servicesconfidentiality, authentication, compression, segmentation, email compatibilitysigning, encryption, both, carried in ordinary mail

The sentence that decides it: they do the same job with different ideas of where trust comes from, from people or from authorities, and that is what makes the comparison a question.

6. Firewall against intrusion detection system

FirewallIntrusion detection system
Actsdecides: permits or refusesreports what it believes it has seen
Positionin the path, a choke pointin the path, or beside it on a mirror port
Knows aboutaddresses, ports, connections, sometimes the requestpatterns of attack, or profiles of normal behaviour
Failureblocks legitimate work, or lets an attack throughfalse alarms, or a missed attack
Catches an insiderno, if they stay insideyes, if their behaviour is unusual
Configured asa rule base, deny by defaultrules or profiles, and then tuned

The sentence that decides it: a firewall is a door and an intrusion detection system is an alarm; the door keeps out what you knew to exclude, and the alarm tells you about what got past it, including from the inside.

How to write one of these in the examination

  1. One sentence of definition for each side, not a paragraph.
  2. A table of four to six rows. Choose the rows that differ; rows that are the same waste the marks.
  3. One concrete figure: 3072 bits against 128, 256 bytes against 32, the tunnel-mode row.
  4. The deciding sentence, which is what the examiner is looking for: when would you use one rather than the other.
  5. Stop. Five marks, a page and a half.

What beginners get wrong here

Describing both sides instead of comparing them. The question says distinguish; a table with rows that differ is the answer.

Saying public-key is "more secure". It is not: at 128 bits of strength both are 128 bits of strength. It solves a different problem, key distribution, and costs far more to do it.

munotes.in676

The Comparisons That Cross Both Modules

Calling a hash a MAC. The key is the difference, and it is the whole difference.

Saying IPsec is safer than TLS. They protect different things; the question is what must be hidden and from whom.

Treating an IDS as a firewall that is bad at its job. It is a different instrument with a different failure mode.

Quick revision

  • Symmetric against public-key: one shared key against a pair; AES-128 equals RSA-3072 in strength (SP 800-57 Table 2); public-key agrees the key, symmetric does the work.
  • MAC against signature: shared key against private key; 32 bytes against 256; only the signature gives non-repudiation.
  • Hash against MAC: no key against a key; an attacker recomputes a hash and cannot forge a MAC.
  • IPsec against TLS: network against transport; a path against a connection; transparent against application-aware; tunnel mode hides the hosts.
  • PGP against S/MIME: web of trust against X.509 authorities.
  • Firewall against IDS: decides against reports; a door against an alarm.

Test yourself

1. Compare symmetric and public-key cryptography. Symmetric cryptography uses a single secret key shared by both parties, so n people communicating pairwise need n(n-1)/2 keys, and it is very fast; public-key cryptography uses a pair of keys, one published and one kept private, so each person needs only one pair, and it is thousands of times slower. For the same security strength the keys are of very different sizes: NIST's table gives 128 bits of strength as AES-128, RSA at 3072 bits, or an elliptic curve of 256 to 383 bits. Symmetric algorithms solve bulk confidentiality; public-key algorithms solve key distribution and make digital signatures possible. In practice the two are used together, public-key cryptography to agree or deliver a symmetric key and then symmetric cryptography for the data.

2. Distinguish a MAC from a digital signature. A MAC is computed with a secret key shared by sender and receiver, so either of them can produce it and either can check it; it gives integrity and authentication between those two parties but not non-repudiation, because neither can prove to anyone else which of them created it. A digital signature is computed with the sender's private key and verified with the corresponding public key, so only the sender can produce it and anyone can verify it, which gives integrity, authentication and non-repudiation. A MAC is much smaller and faster, 32 bytes against 256 for RSA-2048 in the worked example, which is why MACs protect bulk traffic while signatures protect keys, certificates and documents.

3. Why is a hash not enough to protect a message in transit? Because computing a hash requires no key, so an attacker who alters the message can compute the hash of the altered message and replace the old one, and the receiver's check passes. A hash detects accidental corruption, not deliberate alteration. Protection against an attacker requires either a MAC, which mixes in a secret key the attacker does not have, or a digital signature, which requires the sender's private key.

munotes.in677

The Comparisons That Cross Both Modules

4. Compare IPsec with SSL or TLS. IPsec operates at the network layer and protects every packet passing between two points, transparently to the applications, but it must be configured by administrators at both ends; in tunnel mode it hides the whole original packet, including which hosts are communicating. TLS operates above TCP and protects one connection between a client and a server; it requires nothing from the network and is built into browsers and servers, but the application must use it, and the addresses, ports and requested host name remain visible. IPsec protects a path; TLS protects a conversation.

5. Distinguish a firewall from an intrusion detection system. A firewall sits in the path of the traffic and decides, permitting or refusing according to a rule base that ends in deny by default; its failures are blocking legitimate work or admitting an attack it was not told to refuse. An intrusion detection system observes, whether in the path or beside it, and reports what it believes to be an attack, either by matching signatures of known attacks or by comparing behaviour with a profile of normal activity; its failures are false alarms and missed attacks. A firewall cannot detect an insider who stays within permitted traffic, while an intrusion detection system can, if the behaviour is unusual enough. The firewall is a door; the detection system is an alarm.

Contents This chapter on its own page

munotes.in678

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.

Issue
Done!