Cryptyc Tutorial

Contents


What is Cryptyc

Cryptyc (The paper) is a Cryptographic Protocol Type Checker. It is a tool for verifying the correctness of cryptographic protocols. It operates on the typed Spi-Calculus description of cryptographic protocol, annotated with correspondence assertions that encode the desired security properties of the protocol. Cryptyc assumes that all hosts in the protocol are trusted.

In Cryptyc, you define your protocol, and specify certain "effects" called "correspondence assertions" (for instance, "Alice received message"). Cryptyc verifies that the effects are well-formed (more on that later).

This tutorial will describe the process of type-checking a protocol, building up a simple message-passing scheme using a common secret key.


The Spi-Calculus

Spi-Calculus is a way of representing a multiprocess protocol by encoding what each process of the protocol does separately, with communication channels for interaction between the processes.

The following is a simple protocol for Alice passing Bob a message using the shared secret key SKey.

Message 1 Alice --> Bob: {msg}SKey
We want a property that anytime Bob receives this message, Alice must have sent it. In Cryptyc-syntax Spi-calculus, the protocol would be represented as:
client Sender at Alice is {
    establish Receiver at Bob is (socket : Socket);
    new (msg : Payload);
    output socket is ({msg}SKey);
}

server Receiver at Bob is (socket : Socket) {
    input socket is ({msg: Payload}SKey);
} 
So now, we're ready to use Cryptyc to verify this protocol.


Typing the Spi Calculus

Well, not quite. Cryptyc is typed. Some of the types and variables we used in the above case are undefined or undeclared; we need to define the types and declare the variables we use, as follows:
Generic type definitions:
type Payload = Private;

type Socket = Public;
type Host = Public;
type Server = Public;
type Client = Public;
Generic public variables:
public Sender: Client;
public Receiver: Server;

public Alice : Host;
public Bob : Host;
Some protocol-specific types and variables
type MyKey = Key(Payload);
private SKey: MyKey;
And the rest of the protocol, from above:

You need to declare the types of all messages and keys.
type MyKey = Key(Payload);
Declares a the type MyKey of keys capable of decrypting messages of type Payload
private SKey: MyKey;
Declares that SKey is a shared key of type MyKey.


Ready to Use Cryptyc

Usage:

java -jar /home/comp527/cryptyc/cryptyc.jar <filename>
The above example is in /home/comp527/cryptyc/example1.cry. If you run cryptyc on it, it should say the protocol is "OK".
$ java -jar /home/comp527/cryptyc/cryptyc.jar /home/comp527/cryptyc/example1.cry

/home/comp527/cryptyc/example1.cry
Type checked OK!


All Done?

So we wrote a protocol, and Cryptyc says it's OK. But just by looking at the protocol description, we can see that anyone on the network can read the message, replay the message, Alice doesn't know she was talking to Bob, Bob doesn't know it was Alice who sent the message. Why did Cryptyc say OK?


Correspondence Assertions

Cryptyc uses "correspondence assertions" to specify the assurance properties of cryptographic protocols. One legitimate participant in a protocol will assert some property, (for instance, "Bob received the message"), and Cryptyc verifies that some other legitimate participant in the protocol has stated that they are beginning to attempt to achieve the corresponding property.

In this protocol, our security assertion is that Bob's completion of this protocol corresponds to Alice's iniating of this protocol. So Bob's assertion at the end of its protocol run of "Sender sent msg" should correspond to a statement at the start of Alice's protocol run of "Sender sent msg".

These assertions are expressed through begin statements and end assertions. begin and end include some string indicating what is being begun or ended. end assertions are inserted at the point where the property should always be true. begin statements are inserted when you are about to begin the process of achieving some property. When an end assertion is declared, it creates an effect, an obligation that must be satisfied by a corresponding begin.

To verify the correctness of your protocol, Cryptyc checks that during any execution of the protocol, each end assertion's execution must occur after the execution of at least one corresponding one begin statement with the same description tag. Cryptyc runs this analysis assuming an intruder is on the network and could be running any possible Cryptyc program that does not contain any correspondence assertions (i.e. begin and end statements).

Note that it doesn't matter if there are too many begin tags. begin statements don't actually assert anything. They just indicate you would be interested in asserting something later. However, an end statement without a corresponding begin means that a legitimate participant has asserted a property, and no other legitimate participant has attempted to stated that property.

Here is our protocol, with a correspondence assertion added. We want to verify that a sender sent a message.

Event 1 Alice begins msg sent
Message 1 Alice --> Bob: {msg}SKey
Event 2 Bob ends msg sent
And the same protocol as a Cryptyc program.
type Payload = Private;

type Socket = Public;
type Host = Public;
type Server = Public;
type Client = Public;
type Word = Public;

public Sender: Client;
public Receiver: Server;

public Alice : Host;
public Bob : Host;
Note: any words used in begin and end statement descriptions that are not semantically relevant (such as sent) must still be declared, in this case as type Word (which is Public).
public sent: Word;

type MyKey = Key(Payload);
private SKey: MyKey;

client Sender at Alice is {
    establish Receiver at Bob is (socket : Socket);
    new (msg : Payload);
    begin (Sender sent msg);
    output socket is ({msg}SKey);
}

server Receiver at Bob is (socket : Socket) {
    input socket is ({msg: Payload}SKey);
    end (Sender sent msg);
} 
This file is located at /home/comp527/cryptyc/example2.cry. Try running Cryptyc to check the file.
$ java -jar /home/comp527/cryptyc/cryptyc.jar /home/comp527/cryptyc/example2.cry
 
/home/comp527/cryptyc/example2.cry
Type error!
At line 22:
In server Receiver at Bob (socket : Socket ()):
In end (Sender sent msg);
the effect (end (Sender sent msg)) is unjustified.
Effects can be justified either with a matching begin,
or with appropriate nonce checks.


Transferring Effects with Nonces

The problem is that Cryptyc concludes that Bob might assert "end (Sender sent msg)" even though no corresponding begin has been asserted. This is because our protocol doesn't check for the timeliness of a message to prevent replay attacks. To fix this in the protocol, we need to use a nonce.

A nonce is an arbitrary one-time-use-only random bit-string. They are used to establish the uniqueness and timeliness of communications. That is, if Alice sends Bob a nonce, and Bob replies with the nonce encrypted with a shared secret key, Alice knows:

  1. Bob sent the message after Alice sent the nonce
  2. The message is not a replay

We try to fix this protocol by adding nonces.

Event 1 Alice begins msg sent
Message 1 Bob --> Alice N
Message 2 Alice --> Bob: {msg,N}SKey
Event 2 Bob ends msg sent
Here is a Cryptyc program encoding the same protocol.
type Payload = Private;

type Socket = Public;
type Host = Public;
type Server = Public;
type Client = Public;
type Word = Public;
type Challenge = Public;

public Sender: Client;
public Receiver: Server;
public sent: Word;
Note: Struct types are used to encode structures in Cryptyc.
type MyKey = Key (Struct(msg: Payload, nonce: Challenge));

public Alice : Host;
public Bob : Host;

private SKey: MyKey;

client Sender at Alice is {
  new (msg : Payload);
  establish Receiver at Bob is (socket : Socket);
  input socket is (nonce : Challenge); 
  begin (Sender sent msg);
  output socket is ({ msg, nonce }SKey);
}

server Receiver at Bob is (socket : Socket) {
  new (nonce : Challenge);
  output socket is (nonce);
  input socket is ({ msg : Payload, nonce' : Challenge }SKey);
The check statement is used to check that nonces match
  check nonce is nonce';
  end (Sender sent msg);
}
This file is located at /home/comp527/cryptyc/example3.cry.
$ java -jar /home/comp527/cryptyc/cryptyc.jar /home/comp527/cryptyc/example3.cry
 
/home/comp527/cryptyc/example3.cry
Type error!
At line 29:
In check nonce is nonce':
variable nonce' : Challenge ()
is not of nonce type.
Challenge () = Public is not a nonce type.


Nonces in Cryptyc

What went wrong? We sent the nonce, but Cryptyc cannot automatically figure what the nonce is trying to do. We intended it to transfer the assertion "Sender sent msg" from Bob to Alice. We must tell Cryptyc what effects we want to be transferred with the nonce handshake. A nonce handshake transfers an unsatisfied effect from the verifier to another party.

To address this, Cryptyc has a special Nonce type which is used to encode the assertion an effect is trying to encode. The Nonce is a parametric type; there is a single parameter, the end assertion being transferred by the nonce. In the above case, the type would be

Nonce (end (Sender sent msg));
indicating that we are using this nonce to transfer the effect Sender send msg.

We rewrite our protocol using this nonce type:

type Payload = Private;

type Socket = Public;
type Host = Public;
type Server = Public;
type Client = Public;
type Word = Public;
type Challenge = Public;

public Sender: Client;
public Receiver: Server;
public sent: Word;
The statement defining the MyNonce type as a Nonce.
type MyNonce (msg : Payload) = Nonce (end (Sender sent msg));
We also choose to define the message we sent as a type:
type MyMsg = Struct (msg : Payload, nonce : MyNonce(msg));
type MyKey = Key (MyMsg);

public Alice : Host;
public Bob : Host;
private SKey: MyKey;

client Sender at Alice is {
  establish Receiver at Bob is (socket : Socket);
  input socket is (nonce : Challenge);
  new (msg : Payload);
  begin (Sender sent msg);
The cast statement is used extract the unsatisfied effect from the Nonce so that the sender can be matched with the begin. It converts from Untyped to MyNonce(msg)
  cast nonce is (nonce' : MyNonce (msg));
  output socket is ({ msg, nonce' }SKey);
}

server Receiver at Bob is (socket : Socket) {
  new (nonce : Challenge);
  output socket is (nonce);
  input socket is ({ msg : Payload, nonce' : MyNonce(msg) }SKey);
The check statement is used to check that nonces match, enabling an end effect to be used. The syntax is:
check <challenge> is <response>

Where <challenge> is type Challenge (which is Public), and <response> is a nonce-type. Cryptyc won't let you check a nonce more than once.
  check nonce is nonce';
  end (Sender sent msg);
}
This file is located at /home/comp527/cryptyc/example4.cry.
$ java -jar /home/comp527/cryptyc/cryptyc.jar /home/comp527/cryptyc/example4.cry
 
/home/comp527/cryptyc/example4.cry
Type checked OK!
Woo-hoo!


Confirming Sender and Receiver

The effect we tested above just made sure that someone running the protocol ran the appropriate begin message. But what if we want to make sure that Bob can't be tricked into thinking that a message is from Alice when it is from Eve, or we want to make sure that Bob can't be tricked into accepting a message intended for Charlie. To do this, we have to encode the sender and recipient into the effect.

Note that, to achieve this, we also need to pass the sender and recipient in the message. This is what our protocol now looks like.

Event 1 Alice begins Alice sent msg to Bob
Message 1 Bob --> Alice N
Message 2 Alice --> Bob: { Alice,Bob,msg,N}SKey
Event 2 Bob ends a sent msg to b
And here is the Cryptyc version.
type Payload = Private;

type Socket = Public;
type Host = Public;
type Server = Public;
type Client = Public;
type Word = Public;
type Challenge = Public;

public Sender: Client;
public Receiver: Server;
public sent: Word;
public to: Word;

We've added parameters (Hosts a and b) to the nonce, to generate the parametric effect "a sent msg to b" for hosts a and b.
type MyNonce (a: Host, b: Host, msg : Payload) = Nonce (end (a sent msg to b));
Since we need to know who sent the message and who the intended recipient is, we add these parameters to the message Alice sends
type MyMsg = Struct (a: Host, b: Host, msg : Payload, nonce : MyNonce(a, b, msg));
type MyKey = Key (MyMsg);

public Alice : Host;
public Bob : Host;
private SKey: MyKey;

client Sender at Alice is {
  establish Receiver at Bob is (socket : Socket);
  input socket is (nonce : Challenge);
  new (msg : Payload);
  begin (Alice sent msg to Bob);
  cast nonce is (nonce' : MyNonce (Alice, Bob, msg));
  output socket is ({ Alice, Bob, msg, nonce' }SKey);
}

server Receiver at Bob is (socket : Socket) {
  new (nonce : Challenge);
  output socket is (nonce);
  input socket is ({ a: Host, b: Host, msg : Payload, nonce' : MyNonce(a, b, msg) }SKey);
  check nonce is nonce';
  end (Alice sent msg to Bob);
}
This file is located at /home/comp527/cryptyc/example5.cry.
$ java -jar /home/comp527/cryptyc/cryptyc.jar /home/comp527/cryptyc/example5.cry
 
/home/comp527/cryptyc/example5.cry
Type error!
At line 32:
In server Receiver at Bob (socket : Socket ()):
In end (Alice sent msg to Bob);
the effect (end (Alice sent msg to Bob)) is unjustified.
Effects can be justified either with a matching begin,
or with appropriate nonce checks.
Cryptyc is identifying a flaw in this protocol. What Bob doesn't know that a in the message is really Alice. It does know that whomever did send the message would have but their name in it, but it doesn't necessarily have to be Alice. The flaw would be easier to see if we assumed we had an Alice1 and Alice2. Bob would know it was one of them, but not which. Alice begins "Alice sent msg to Bob", but only passes to Bob the effect "a sent msg to b". As a result, Bob must end "a sent msg to b"

The file /home/comp527/cryptyc/example6.cry ends Bob with

end (a sent msg to b);
$ java -jar /home/comp527/cryptyc/cryptyc.jar /home/comp527/cryptyc/example6.cry
 
/home/comp527/cryptyc/example6.cry
Type checked OK!


Union Types

Crypto keys are tagged with what they can encrypt. If you want to encrypt two different messages using the same key or send two different messages over the same channel, then use what is called a union type. A variable that is a union type is exactly one of multiple different types. Consider this silly extension to our protocol:
Event 1 Alice begins Alice sent msg to Bob
Message 1 Bob --> Alice N
Message 2 Alice --> Bob: {Alice,Bob,msg,N}SKey
Event 2 Bob ends a sent msg to b
Message 3 Alice --> Bob: {Alice,Bob,msg}SKey
type Payload = Private;

type Socket = Public;
type Host = Public;
type Server = Public;
type Client = Public;
type Word = Public;
type Challenge = Public;

public Sender: Client;
public Receiver: Server;
public sent: Word;
public to: Word;

type MyNonce (a: Host, b: Host, msg : Payload) = Nonce (end (a sent msg to b));
These are our two message types
type MyMsg = Struct (a: Host, b: Host, msg : Payload, nonce : MyNonce(a, b, msg));
type MyMsg2 = Struct (a: Host, b: Host, msg: Payload);
This is the union type, taking both messages. The secret key encrypts this union type. Note that each type in the union type has a label (msg and msg2).
type UseMsg = Union (msg: MyMsg, msg2: MyMsg2);

type MyKey = Key (UseMsg);

public Alice : Host;
public Bob : Host;
private SKey: MyKey;

client Sender at Alice is {
  establish Receiver at Bob is (socket : Socket);
  input socket is (nonce : Challenge);
  new (msg : Payload);
  begin (Alice sent msg to Bob);
  cast nonce is (nonce' : MyNonce (Alice, Bob, msg));
We now need to tell Cryptyc what type is being encrypted. The syntax for encrypting a union type is {label(fields,...)}Key.

In this case, we are encrypting something of type MyMsg.

  output socket is ({msg( Alice, Bob, msg, nonce') }SKey);

  new (msg: Payload);
In this case, we are encrypting something of type MyMsg2.
  output socket is ({msg2(Alice, Bob, msg)}SKey);
}

server Receiver at Bob is (socket : Socket) {
  new (nonce : Challenge);
  output socket is (nonce);
  input socket is ({ msg(a: Host, b: Host, msg : Payload, nonce' : MyNonce(a, b, msg)) }SKey);
  check nonce is nonce';
  end (a sent msg to b);

  input socket is ({msg2(a : Host, b: Host, msg: Payload)}SKey);
}
This file is located at /home/comp527/cryptyc/example7.cry.


Message Confirmation

Now we can do something a little more complex. Bob knows that Alice sent the message, and it was intended for Bob. However, Alice does not know that Bob received the message. We modify our protocol so Bob sends an acknowledgment:
Event 1 Alice begins Alice sent msg to Bob
Message 1 Bob --> Alice N
Message 2 Alice --> Bob: {Alice,Bob,msg,N,N2}SKey
Event 2 Bob ends a sent msg to b
Event 3 Bob begins Bob got msg from Alice
Message 3 Bob --> Alice: {Alice,Bob,msg,N2}SKey
Event 4 Alice ends b got msg from a
This is the Cryptyc form of the same protocol:
load = Private;

type Socket = Public;
type Host = Public;
type Server = Public;
type Client = Public;
type Word = Public;
type Challenge = Public;

public Sender: Client;
public Receiver: Server;
public sent: Word;
public to: Word;
public got: Word;
public from: Word;

type MyNonce (a: Host, b: Host, msg : Payload) = Nonce (end (a sent msg to b));
type MyNonceR (a: Host, b: Host, msg : Payload) = Nonce (end (b got msg from a));
type MyMsg = Struct (a: Host, b: Host, msg : Payload, nonce : MyNonce(a, b, msg), nonceR : Challenge);
type MyMsgR = Struct (a: Host, b: Host, msg : Payload, nonce : MyNonceR(a, b, msg));
type UMsg = Union (msg: MyMsg, msgR: MyMsgR);
type MyKey = Key (UMsg);

public Alice : Host;
public Bob : Host;
private SKey: MyKey;

client Sender at Alice is {
  establish Receiver at Bob is (socket : Socket);
  input socket is (nonce : Challenge);
  new (msg : Payload);
  begin (Alice sent msg to Bob);
  cast nonce is (nonce' : MyNonce (Alice, Bob, msg));
  new (nonceR : Challenge);
  output socket is ({msg(Alice, Bob, msg, nonce', nonceR)}SKey);
  input socket is ({msgR(a : Host, b : Host, msg : Payload, 
                         nonceR' : MyNonceR(a, b, msg))}SKey);
  check nonceR is nonceR';
  end(b got msg from a);
}

server Receiver at Bob is (socket : Socket) {
  new (nonce : Challenge);
  output socket is (nonce);
  input socket is ({ msg(a: Host, b: Host, msg : Payload, 
                         nonce' : MyNonce(a, b, msg) , 
                         nonceR: Challenge)}SKey);
  check nonce is nonce';
  end (a sent msg to b);
  begin (Bob got msg from Alice);
  cast nonceR is (nonceR' : MyNonceR(Alice, Bob, msg));
  output socket is ({msgR(Alice, Bob, msg, nonceR')}SKey);
}
This file is located at /home/comp527/cryptyc/example8.cry.

Here we are exchanging two nonces and transferring one effect in each direction in each nonce handshake.


Public Key Cryptography

Cryptyc only understands symmetric key cryptography; two nodes must already have a shared secret in order to communicate.


References

These types exist:
Public
Private
Key(T)
Struct (foo: T1, bar: T2, baz: T3)
Union (tag1: T1, tag2: T2, tag3: T3)
Nonce (end (M))
These statements exist:
establish Receiver at Bob is (socket : Socket);

new (msg : Payload);

output socket is ({msg}SKey);
input socket is ({msg: Payload}SKey);

begin (Sender sent msg);
end (Sender sent msg);

cast nonce is (nonce' : MyNonce (msg));
check nonce is nonce';
You're not limited to 2 entities in a protocol. You can have as many as you want. (Eg, one or more intermediaries). The input and output commands perform pattern matching.


Some More Examples

There are some examples of Cryptyc protocol descriptions in /home/comp527/cryptyc/examples. This includes some protocols that fail to type-check for a variety of reasons and some that succeed. There are some more complicated protocols included as well, and these tend to have descriptions in arrow-notation as well as the Cryptyc syntax. These examples do a good job of illustrating what you can do with Cryptyc.


Fall 2004 updates:

scrosby@cs.rice.edu, Department of Computer Science, Rice University

Fall 2003 author:

arudys@rice.edu, Department of Computer Science, Rice University
Last modified: Fri Sep 26 15:06:29 CDT 2003