ted.neward@newardassociates.com | Blog: http://blogs.newardassociates.com | Github: tedneward | LinkedIn: tedneward
learn about cryptography in general
learn how the JVM provides cryptographic services
"secret writing" (Greek)
kryptos: hidden
graphos: communication/writing
transforming plain text into ciphertext (and back again)
an art and science that has been developing for thousands of years
do not confuse with steganography (obscured message)
"obscured" writing
example: I write a message in plain text but hide it in a painting
many stories around "cryptography" involve both forms
hide the message
the message is in code
very popular movie trope (DaVinci Code, National Treasure, etc)
Alice: first party, usually sender/originator
Bob: secod party, usually receipient
Mallory: malicious user, seeks to change the message
Eve: eavesdropper, seeks to read the message
Trent: trusted third-party
Java has long had a rich security footprint
JDK 1.1 introduced "Java Cryptography Extensions" (JCE)
JDK 1.2 and later refined and improved with each release
Cryptography was export-controlled
So Sun broke Java support into two packages
java.security
: Non-controlled (part of core JDK)
javax.crypto
: Export-controlled portions (removable)
By then, crypto export controls not an issue (as much)
Oracle reorg'ed everything across five modules:
java.base
: Foundational APIs
java.security.jgss
: Support for GSS API (Kerberos)
java.security.sasl
: Support for SASL
java.xml.crypto
: XML cryptography
jdk.jartool
: for signing JAR files
jdk.security.auth
: implementations of authentication
jdk.security.jgss
: extensions to GSS API
Interfaces define access/functionality
Providers provide interface implementations
Allows for independence and extensibility
third-party cryptography providers
new algorithms come online/proven out
However, it means indirect construction and usage
Typos can really ruin your entire day
abstraction over different implementations
default implementation ("JKS") is a file-based store
manage three kinds of entries:
private keys
secret keys
trusted certificates
always password-protected
default implementation of KeyStore
shipped with JDK 1.1
used frequently for any key storage
creating signed JARs
for digitally-signing Android applications
but unused anywhere outside of Java
define an algorithm
define a key to that algorithm
encrypt(plaintext, key) -> ciphertext
decrypt(ciphertext, key) -> plaintext
"A good cryptosystem is one in which all the security is inherent in knowledge of the key and none is inherent in knowledge of the algorithm." (Applied Cryptography, section 2.2)
substitute plain for cipher and back again
encryption: plain X -> cipher Y
decryption: cipher Y -> plain X
strength: simplicity of algorithm (mapping of P to C)
strength: simplicity of key (the mappings themselves)
weakness: key management
weakness: easy cryptanalysis
apple -> aardvark, banana -> beagle, ...
WW2: Imperial Japanese Navy, Midway -> AF
strength: we don't have to encrypt everything; fast
strength: we can often pass ciphertext as plaintext
weakness: easily contextualized
"the crow flies at midnight!" that's not common speech
"attack the smurfs!" maybe we should figure out who the smurfs are
weakness: key management ("code book")
weakness: chosen-plaintext attack
strength: simplicity of algorithm
weakness: complexity of key (dictionary of words)
weakness: key management
algorithm: rotate along a wheel or line
key: # of characters left or right
algorithm: code word + regular ordering after that
key: code word(s) ("ROCKETFAMILY")
Keys must be distributed in secret
Compromised keys are worse than no keys
Eve can decrypt all message traffic encrypted with that key
Eve can pretend to be one of the parties and produce false messages
Mallory could doctor the messages in-flight and re-send
Key pairs for every pair of users grow exponentially
n users requires n(n − l)/2 keys
10 users require 45 different keys to talk with one another
100 users require 4950 keys
SecretKey
interface
KeyGenerator
factory
Cipher
class
lots of different symmetric-key algorithms
DES, AES, and more
stick with AES (unless you know enough to differentiate)
use KeyGenerator
to create an instance based on algorithm
before use, intialize the KeyGenerator
via init
, passing key size (128)
can now generateKey()
to obtain a SecretKey
instance
this is the key, and key management kicks in!
we can obtain the raw byte[]
for the key using getEncoded()
SecretKeySpec
is useful for representing/transporting key material
Generating a SecretKey
// Generate secret key KeyGenerator keygen = KeyGenerator.getInstance(ALGORITHM); keygen.init(128); SecretKey aesKey = keygen.generateKey(); // Store into args[0] try (FileOutputStream fos = new FileOutputStream(args[0] + ".key")) { byte[] rawKey = aesKey.getEncoded(); fos.write(rawKey); }
typically it's just a parameter to another call
such as using the Cipher
to encrypt/decrypt
remember, though, it's the critical piece to the process
"strength lies in the key not the algorithm"
use Cipher.getInstance()
based on algorithm
initialize (init()
) to either encrypt or decrypt along with SecretKey
pass data in via update
calls
last call must be doFinal
, which returns encrypted data stream
Encrypting
Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] cipherdata = cipher.doFinal(cleartext);
Decrypting
Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, secretKey); byte[] plaindata = cipher.doFinal(ciphertext);
CipherInputStream
: wraps around an InputStream
w/a Cipher
CipherOutputStream
: wraps around an OutputStream
w/a Cipher
each is then used in the usual (I/O stream) way
relatively easy to compute, but significantly harder to reverse
that is, given x it is easy to compute f(x)
but given f(x) it is hard to compute x
breaking a plate is a good example
takes a variable-length input string (called a pre-image) and converts it to a fixed-length (generally smaller) output string (called a hash value)
take a file and return its length in bytes
or take a string and return a cumulative XOR of all bytes
in essence, hash(long-input) -> short-result
goal: produce a value that indicates whether a candidate pre-image is likely to be the same as the real pre-image
hash function that works in one direction
easy to compute a hash value from pre-image
hard to generate a pre-image that hashes to a particular value
good one is also collision-free
hard to generate two pre-images with the same hash value
aka data authentication code (DAC)
one-way hash function with the addition of a secret key
only someone with the key can verify the hash value
a numeric representation of a message computed by a cryptographic hash algorithm or a function
a fixed-size content-based hash of a document of any size
if the content changes, the hash changes in turn
hash collisions are possible, but rare
moreover, hash collisions would come from wildly-varying input
used to assert that the content is the same as the time the hash was generated
MD5 hashes have 32 characters
SHA1 hashes have 40 characters
MD5, SHA1 most commonly used; others may vary in size
java.security.MessageDigest
(abstract class)
provider-based implementation; use getInstance
to create one
algorithm name passed to getInstance
(eg. "SHA-256")
construct MessageDigest
pass data in via update
calls
when complete, call digest
to get result of completed hash computation
reset
starts over, for calculating a new digest
imagine an algorithm such that
Alg(plain, K1) -> cipher
Alg(cipher, K1) -> garbage
Alg(cipher, K2) -> A
K1,K2 are "pairs" and can only work together
one I give out to everyone: public key
one I never trust to anyone: private key
think of it as a locked mailbox
anyone can put mail into the box
only with the key can get you get mail out
encrypt a message with a private key
only the corresponding public key can decrypt it
this verifies it was sent by the private key owner
encrypt a message with a public key
only the corresponding private key can decrypt it
this verifies that only the private key owner can read it
(this option is considered less secure, but still works)
public keys can thus be sent over untrusted media
public-key algorithms are not a replacement for symmetric ones
slow: generally 1000 times slower than symmetric algorithms
vulnerable to chosen-plaintext attacks
KeyPair
/PublicKey
/PrivateKey
KeyPairGenerator
Cipher
lots of different symmetric-key algorithms
RSA, DSA, Diffie-Hellmann, and more
stick with "RSA" (unless you know enough to differentiate)
use KeyPairGenerator
to create an instance based on algorithm
before use, intialize the KeyPairGenerator
via init
, passing key size (2048)
can now generateKeyPair()
to obtain a KeyPair
instance
public key can be shared freely; private key must be squirreled away!
Generating a SecretKey
// Generate pub/priv key KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGORITHM); kpg.initialize(2048); KeyPair pair = kpg.generateKeyPair();
do you want the public key, private key, or both?
getPublic()
gives public key
getPrivate()
gives private key
each can have raw bytes retrived via getEncoded()
restore public key from bytes using X509EncodedKeySpec
restore private key from bytes using PKCS8EncodedKeySpec
regardless of public or private, these are just parameters to pass
such as using the Cipher
to encrypt/decrypt
remember, though, the key is the critical piece to the process
"strength lies in the key not the algorithm"
use Cipher.getInstance()
based on algorithm
initialize (init()
) to either encrypt or decrypt along with key
pass data in via update
calls
last call must be doFinal
, which returns encrypted data stream
Encrypting
Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, privateKey); byte[] cipherdata = cipher.doFinal(cleartext);
Decrypting
Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, publicKey); byte[] cipherdata = cipher.doFinal(cleartext);
CipherInputStream
: wraps around an InputStream
w/a Cipher
CipherOutputStream
: wraps around an OutputStream
w/a Cipher
each is then used in the usual (I/O stream) way
trusted means of obtaining public keys
verified by trusted sources ("certificate authorities")
in a hierarchical manner ("certificate chains")
"authentic": signature convinces others the document was deliberately signed by signer
"unforgeable": only the signer--and no one else--could produce this signature
"not reusable": signature is part of the document and cannot be re-purposed/moved
"unalterable": after the document is signed, it cannot be altered
"unrepudiatable": signature and document are physical things, evidence that the signer did sign it
None of this is actually true, of course
we want a similar kind of system, but...
computer files are trivial to copy
documents permit frequent cut-and-paste of contents
computer files are easy to modify post-signature
and leave no trace of modification
so we can't just use handwritten signatures "lifted naively" into documents
what we want is to know the original document
was not altered
came from the source
so if we run a one-way hash, using something unique to source
and the same calculation works, it must be unaltered/original
Symmetric Crypto + Arbitrator
Alice wants to sign a digital message and send it to Bob
Trent + symmetric crypto = success!
Trent shares secret keys
Ka to Alice
Kb to Bob
Trenth then insists on the following process:
Alice encrypts her message with Ka (E(Ka, P) -> X1), sends it to Trent
Trent decrypts X1: D(Ka, X1) -> P
Trent encrypts P and a receipt using Kb E(Kb, P + R) -> X2, sends it to Bob
Bob decrypts X2: D(Kb, X2) -> P + R
We know it's from Alice, and can't be altered
authentic: Trent is trusted, and Trent knows Alice sent P
unforgeable: Only Alice and Trent knows Ka; only Alice and Bob knows Kb
unreusable: Bob's attempt to take Trent's certification and reuse it will be obvious (he could not produce Ka)
unalterable: Bob's attempt to modify P would be obvious (Trent could prove the difference)
unrepudiatable: Alice cannot repudiate, because Trent has a copy
but what if Trent becomes untrusted?
Trent also is spending a lot of time serving as a middleman for communication
the collection of secret keys must be absolutely secure
Alice encrypts document P with her private key VKa: E(VKa, P) -> X
Alice sends X to Bob
Bob decrypts ciphertext X using Alice's public key PKa: D(PKa, X) -> P
no Trent needed
only Alice's public key will decrypt X
any attempt to decrypt-and-reencrypt will not yield P when using PKa
Bob could decrypt the message multiple times
not a problem if it's a contract, but what if it's a digital check/transaction?
for this reason most digital signatures include timestamps
asymmetric key crypto is slow
encrypting/decrypting all of P could be long if P is big
Alice runs a one-way hash (digest) of P to produce Ph
Alice encrypts Ph with her private key VKa: E(VKa, Ph) -> Xh
Alice sends P and Xh to Bob
Bob decrypts ciphertext Xh using Alice's public key PKa: D(PKa, Xh) -> Ph
Bob runs one-way hash of P and compares it against Ph
if they match, it's verified
if they don't, Bob knows Ph was not produced from P
Digital Signatures!
Signature
abstract class
provider-based implementation
signature requires use of PrivateKey
validation requires use of PublicKey
remember this is not encryption! this is tamper-detection
many algorithms available
stick with a well-known one ("SHA256withRSA")
if you can list others, feel free to use them
but keep in mind, arguing between algorithms is generally pretty unnecessary
get a Signature
instance via getInstance()
passing in algorithm
initialize signature (initSign
) with the PrivateKey
to use to sign the document
pass the data in via update
(repeatedly if necessary)
when finished, call sign
the return from sign
is the digital signature
Signing a document
Signature privateSignature = Signature.getInstance("SHA256withRSA"); privateSignature.initSign(privateKey); privateSignature.update(cleartext); byte[] signature = privateSignature.sign();
get a Signature
instance via getInstance()
, passing in algorithm
initialize verification (initVerify
) with the PublicKey
of the pair
pass the document in via update
(repeatedly)
when finished, call verify
passing in signature
if true, passed! If false, tampering detected!
Verifying a document
Signature publicSignature = Signature.getInstance("SHA256withRSA"); publicSignature.initVerify(publicKey); publicSignature.update(cleartext); if (publicSignature.verify(signatureBytes)) { System.out.println("Verified!"); } else { System.out.println("NO MATCH"); }
The Code Book (Singh; Anchor, 2000)
A history of encryption with surprisingly consumable detail
Applied Cryptography, 2nd Ed (Schneier; TBS, 1995)
Old, but comprehensive book on cryptography and related subjects
NOTE: This book was at one point subject to export constraints
Practical Cryptography (Schneier; Wiley, 2003)
Schneier's mea culpa after Applied Cryptography was published
Practical Cryptography
http://practicalcryptography.com/
An Overview of Cryptography
https://www.garykessler.net/library/crypto.html
Architect, Engineering Manager/Leader, "force multiplier"
http://www.newardassociates.com
http://blogs.newardassociates.com
Sr Distinguished Engineer, Capital One
Educative (http://educative.io) Author
Performance Management for Engineering Managers
Books
Developer Relations Activity Patterns (w/Woodruff, et al; APress, forthcoming)
Professional F# 2.0 (w/Erickson, et al; Wrox, 2010)
Effective Enterprise Java (Addison-Wesley, 2004)
SSCLI Essentials (w/Stutz, et al; OReilly, 2003)
Server-Based Java Programming (Manning, 2000)