One-time pad crypto systems

December 31, 2006

Tijmen van den Brink , Ralph Koning
Daniël Sánchez and Maurits van der Schee

Universiteit van Amsterdam

0 Abstract

One-time pad (OTP) is an improved Vernam cipher. Joseph Mauborgne improved the system by adding a truly random key. OTP is a cipher based on the XOR operation with a key as large as the message and a key that may not be reused. Claude Elwood Shannon, a mathematician, proved it uncrackable in the forties. Recent studies[6] have shown that the key-length of the transmitted OTP can be reduced without violating the properties of a perfect crypto system. On the Internet the 20 bytes IP header would render this method almost useless. OTP may be uncrackable, but there are considerations when implementing a crypto system based on it. A big problem is that truly random numbers are hard to produce. You must also make sure keys are not used twice and the very large keys must be exchanged over a secure channel. Naive implementations may be vulnerable to adjustment of known plaintext and cannot detect tampering with the message. We found one company (Mils electronic) that sells crypto systems based on OTP. We've suggested two other applications for OTP crypto systems. One is file system encryption, because file system encryption doesn't require the distribution of keys to different parties. The other is a TCP tunnel secured by OTP. We actually implemented the tunnel software as a proof-of-concept in Ruby (see Appendix A). This software tries to simplify and automate the key management. When you have just a few parties to communicate with, you need a high performance and you don't want your messages ever to be readable (by someone without a key), an OTP crpyto system might be the right choice.

1 Introduction

In this paper we will try to give a full overview of one-time pad encryption. We discuss it's history, the theory behind it, some real life applications and some other applications. We have worked out a file system encryption application and secure TCP tunneling application in great detail. At the end of this paper we try to conclude under which circumstances and for what applications one-time pad crypto systems can be useful.

2 Scientific context

In this chapter we will discuss the theory of OTP (one-time pad). It will contain a lot of history, because most of the research is done in the first half of the last century. We will start with an historic overview of important events concerning OTP:

Unfortunately there is not much recent research into one-time pad crypto systems. This may be caused by the success of public key encryption, but may also be caused by the triviality of the system or the lack of applications. We have found one publication on the subject that we will discuss.

2.1 Theory

About ninety years ago Gilbert Vernam, who worked at AT&T invented a crypto algorithm later to be proven uncrackable by introducing some conditions we will discuss later. The Vernam cipher as it was called combines plaintext with a random key. The key consists of a random stream of characters. Each of these characters represent the number of places the corresponding character in the plaintext should be shifted. This can be used with every subset of characters. Most commonly used are of course the latin-alphabet [A-Za-z] and for digital messages the random strings consisting of 0’s and 1’s. Note that with the Vernam cipher a key could be used several times as in a loop. This caused the crypto algorithm to be crackable.

Joseph Oswald Mauborgne, a college of Gilbert Vernam at AT&T recognized that if the key would be completely random, and thus not being reused, cryptanalytic difficulty would be increased. Note that this implies:

| key | < | message | (1)

This new condition gave birth to the crypto algorithm called the one-time pad. But still the cipher was not proven to be uncrackable. This was only discovered 25 years later by Claude Elwood Shannon, an American electrical engineer and mathematician. He recognized and proved the theoretical significance of the one-time pad crypto algorithm.

Let’s illustrate how the one-time pad crypto algorithm works. We suppose Alice wants to send a secret message M = ATTACKATDAWN to Bob. With the use of for example a dictionary she converts this string into the bits M = 01001110110110. The next thing to do is to generate a random key of at least the same number of bits as the message (see 1.1). K = 11011100011100. Alice and Bob need to be securely issued this key. They now have two identical keys which they can use to encode or decode the message. She now performs a XOR operation to encrypt the message.

C = M XOR K (2)

This would result in the following using K = 11011100011100 as the key and M = 01001110110110 as the message:

K = 11011100011100
M = 01001110110110 XOR
C = 10010010101010

Alice now can send this encrypted message to Bob. For Bob to decipher this message the following equation is used:

M = C XOR K (3)

Bob would now decrypt the message like this:

K = 11011100011100
C = 10010010101010 XOR
M = 01001110110110

2.2 Recent developments

Recent studies[6] have shown that the key-length of the transmitted OTP can be reduced without violating the properties of a perfect crypto system. The reduction is in many cases small but for cryptosystems that use OTP on a regular basis it can be very useful. Figure 2 shows how the key length can be reduced in the case sender and receiver know they are exchanging 10 bit messages.

otp revisitedFigure 1: Key length reduction scheme

Even though the above is theoretically correct, implementing it may not always help you much. If the channel that the message is sent over is for instance the Internet you will have to signal after every 10 bit message that the message has ended, because not every 10 bit message takes the same amount of bits in communication and you must be able to separate two adjacent messages. There is no cheap way to do this unless the underlying layer in your protocol stack provides you this kind of signalling without too much overhead.

In the Internet example the IP header length (of 20 bytes) would render this method almost useless in a TCP stream or UDP packet. You safe a few bits at a cost of 20 bytes. Of course you can always apply this trick to the last 10 bit message you send in an IP packet. The package length in IP is expressed in bytes. If you send 10 bit messages, you can save a byte in an IP packet if a trailing zero occurs. Per message that has a trailing zero this would save you:

1 byte you win / (2 byte message + IP header length)=1/(2+20)=1/22

If you multiply that with the probability that this happens you find the best possible optimization on IP.

0.5*1/22=1/44=2,3% (rounded)

This key reduction scheme could save you a maximal average of 2,3% of data if implemented on IP. This is with very theoretical optimal conditions. In an (more realistic) example with messages of 1024 bits long this will save you:

0.5^7*1/(128+20)=0,053‰ (rounded)

This is not much: about 55 bytes per megabyte and not nearly as much as the 10% average that is implied in figure 1.

2.3 Is OTP uncrackable?

In the first part of this chapter we have seen that OTP is proven uncrackable, so we should say `yes'. But there are some side notes to this which should be mentioned. To be really implement it secure, some important factors are demanded (see also chapter 3 about the security of a crypto system).

Things that should be considered:

3 Characteristics of a good crypto system

”Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.” - Antoine de Saint-Exupery (1900 - 1944)

Almost sixty years ago the basis of a good crypto system was set by C.E. Shannon. The next section will cover the most important topics that can be used to valuate a crypto system.

3.1 Shannon Characteristics

To determine the value of a crypto system we’ll use Shannons valuations of crypto systems[9]. These are the most imported ones:

3.2 Confusion and Diffusion

There are two more concepts that need to be discussed; Confusion and diffusion. If an algorithm should transform a plaintext into a ciphertext in such a way the enemy could not possibly know or predict what a change in the plaintext would do to the ciphertext, then we then talk about confusion [9]. A mono alphabetic substitution cipher, like the Ceaser cipher, is an example which provides no confusion whatsoever. In contrast to a polyalphabetic substitution cipher, like the Vigenre cipher, that provides great confusion.

Diffusion is the concept of spreading the information from the plaintext over the entire ciphertext. This would result in changes all over the ciphertext if a small change is made to the plaintext. The greater the diffusion the bigger the amount of material (e.g. ciphertext) needed to break the algorithm.

3.3 Definition of Perfect Secrecy

perfect secrecy
Figure 2: Perfect system (source: [9])

Claude Elwood Shannon defined perfect secrecy as the condition that for all possible cryptograms the a posteriori probabilities1 are equal to the a priori probabilities2 independently of the number of messages and the number of possible cryptograms. Figure 2 illustrates a perfect system and shows us that a cryptanalyst intercepting an encrypted message E has no information because the number of a priori probabilities remains the same. Another necessity to obtain perfect secrecy is the fact that there have to be as many cryptograms as there are messages. And for every message there must be a key that transforms the message in at least one of the cryptograms or preferably as illustrated by figure 2 where all the messages transform to all the cryptograms. As we wrote earlier the OTP has these properties and thus achieves perfect secrecy. Till now it is the only proven crypto algorithm to meet all the Shannon Characteristics.

1 These are probabilities after deduction of false probabilities from facts
2 These are the probabilities based on hypothesis rather than experiment

3.4 How does OTP fit in Shannon's demands?

Now we shall look at Shannon's demands and how OTP fit in them.

4 Real life applications

On many sites on the Internet you can read that one-time pads can be, and possibly are, used for military strength encryption. We tried to find the companies that sell one-time pad crypto systems and we have found only one: Mils.com. This may be caused by the closed nature of the military or by us not searching good enough.

In this chapter we discuss the systems provided by Mils.com and we discuss the stream cipher RC4 and its current most popular application: WEP. We discuss a stream cipher, because a one-time pad is crypto system is also a stream cipher and we might learn from the errors that are made in its implementation. We discuss this particular stream cipher, because it is very popular and well documented.

4.1 Mils.com

Mils.com is the only company we could find that really focuses itself on OTP technology. Although they call it OTK (One Time Key) on their website there’s a page explaining that the techniques are actually the same. They also claim to provide unbreakable security. Mils delivers four main products, MilsFile, MilsMail, MilsMessage and MilsGenerator.

All products are heavily reliant on the MilsCard, this is a hardware device containing a random number generator that uses a random noise source to generate the keys. Besides some other encryption methods Mils uses this cards random number generator to provide the One Time Keys. More information about the randomness in [1]

The One Time Keys are stored on a removable storage encrypted using the DPS (Digital Protection System). DPS is a propiatary algorithm that produces a key stream based upon some predefined keys, the keys for this algorithm are available on the MilsCard.

With the One Time Keys generated messages can be encrypted. The encryption is based on the true principles of OTP by using a key of the same size as the data and by using the key only once. More info about this in [2]

Although this method seems pretty secure. The problem lies within the key distribution. The One time Keys are secured with the proprietary DPS algorithm. And because this algorithm is proprietary the security of this algorithm can’t be verified by anyone but Mils. So the encryption system is as secure as their proprietary DPS algorythm.

Although this method is more secure than storing the keys in plaintext, this also delivers a false sense of security. Instead of transporting the keys in a perfectly secure way, people are going to rely on the DPS algorithm. A solution to this is to do both, making the DPS algorithm pretty useless.

4.2 RC4

RC4 is a stream cipher developed by Ron Rivest of RSA Security in 1987. This algorithm generates an pseudo random output stream given two input vectors. This pseudo random string can be used as the one-time pad. Although this is a pseudo random string instead of a truly random stream an algorithm can do a very good job for generating one-time pads. RC4 has become very popular because of the efficiency. And still, though it’s considered insecure now, it’s being used in many embedded devices because of it. The insecure part the cipher is mostly due to it’s key scheduling [3] also there were patterns found in the randomness of the output [4]. Besides the fact RC4 heavily relies on the OTP technology there are also things we can learn of implementations of RC4 we have to look at while implementing OTP.

4.3 WEP

WEP (Wired Equivalent Privacy) is an algorithm used for the protection of data transmitted over 802.11 based wireless connections [5]. WEP is using the RC4 cipher for confidentiality. This implies WEP is also vulnerable for the attacks on RC4. But it has some other problems with it’s implementation, the keys it’s using are too short and the Initialization Vectors it’s using as input for the RC4 algorithm is send as plaintext over the connection. Nowadays there are various active and passive attacks available which can break this encryption within a matter of minutes [7]. Besides encryption WEP also uses CRC-32 for integrity [8]. This algorithm calculates a checksum over the data send. This checksum is send along with the data to ensure the receiver side can verify the data didn’t get corrupted during the transmission. This is an important thing to think about implementing OTP because OTP on itself doesn’t provide any form of integrity protection. WPA (Wireless Protected Access) the successor of WEP tries to patch some of the weaknesses of and also changed the method of integrity checking because it was considered insufficient. WPA2 [10] supersedes WPA and is nowadays considered secure.

5 Other applications

Although just a plain XOR would be perfectly secure, as argued earlier, it wouldn't make a perfect crypto system. Such a naive implementation will - just like any other stream cipher (see [11]) - be vulnerable to an attack where the enemy can adjust known plaintext. Another problem is that if the enemy alters the cipher text, there is no way to detect that. And it suffers from the problem that the message length is exposed.

The first two problems can be solved by adding a Message Authentication Code (MAC) to the message and the third problem can be solved by padding the message with a random number of zero's. Next we will discuss two crypto system designs we came up with ourselves.

5.1 File system encryption

"What a one-time pad system does is take a difficult message security problem ... and turn it into a just-as-difficult key distribution problem." - Bruce Schneier

Bruce Schneier argues in [12] that key distribution is the biggest problem in an one-time pad implementation. File system encryption differs from encryption of point-to-point communication in that there is no other trusted party to share a key with. This would make file system encryption the perfect application for one-time pad encryption, because file system encryption doesn't require key distribution between two parties.

Opponents of our idea may argue that such a large key is ridiculous and/or that it isn't feasible to produce random data at a rate that is sufficient for disk I/O. The problem with the key length is arbitrary: nowadays you can buy a very portable 8 gigabyte USB stick for under $200. A lot of secrets fit on a 8 gigabyte encrypted partition. As for the speed: There are hardware devices (TRNG's) available that produce up to 30 Mbit of true random data per second [14]. If you let the device produce these numbers continuously and store them for later use in a very large buffer, you must be able to solve the I/O speed problem.

We came up with the idea to create a USB device that contains a lot of flash memory, a true random number generator and a microprocessor. It looks like a USB drive, but it doesn't provide the system it connects to with access to its memory. In stead it offers two functions:

The microprocessor should delete the key of a block from the memory after successful decryption to ensure that the key is not read unnoticed - by an enemy that temporary got hold of the device - by sending zero's to the decrypt function. Of course you could argue that if an enemy gets hold of the stick that this enemy will read the memory physically and not programmatically and therefore the deletion of the key and step 3 and 4 of the read scheme (see below) can be skipped. Designing this device in such a way that physical tampering with it can be detected justifies these extra steps, because then physical and programmatical reading of the stick can be detected.

# read # write
1 data=read(blocknr)
1 data=encrypt(blocknr,block)
2 block=decrypt(blocknr,data)
2 write(blocknr,data)
3 data=encrypt(blocknr,block)
   
4 write(blocknr,data)    
Figure 3: Schemes for a read and write of a disk block

To prevent adjustments to known plaintext a MAC for every block should be stored. Fortunately the message length is not exposed, because the entire file system (partition) is encrypted.

We tried to alter TrueCrypt (see [13]). This is free open-source disk encryption software for Windows XP/2000/2003 and Linux. The Windows source code comes with a file system driver. By reading it's source code we found out that TrueCrypt uses a filter driver to operate.

A filter driver is a driver on top of a normal driver that filters the Windows I/O request packets (IRP's) that go to or come from an underlaying driver. The filter driver should support the same IRP's as the underlying driver it is attached to and can modify these IRP's. It is easy to see that this can be used to add functionality to an existing driver. TrueCrypt's filter driver encrypts and decrypts the data in the IRP's.

We weren't able to do a quick and dirty implementation, because filter drivers are kernel drivers and should therefore be implemented with care and good planning and our time was limited.

5.2 TCP tunnel encryption

Remote shell was the first application for Internet streams and today secure shell (SSH) is still an important application that is used a lot. We don't believe that SSH will be broken, but what if?

We've tried to build a solution to that problem and we've called it "otptun". We designed TCP tunnel software based on the proven one-time pad encryption algorithm. In this design we've tried to solve the synchronization problem that is described in [15]. It can be used in the unlikely case that SSH is broken or it can be used to secure any other protocol that uses a single TCP connection.

5.2.1 The otptun protocol

Both the client and server listen on port 1027 (the first unused port above 1024 according to ICANN). They both forward messages. The otptun client listens only on the localhost while the otptun server listens on all interfaces. The client forwards between the real client and the otptun server and the server forwards between the otptun client and the real server. The server and client both encrypt when they send to each other and decrypt if they receive from each other.

All tunnels are client initiated. A client connects to the otptun client. The otptun client then connects to the otpserver and sends a first message. It is a 34 byte message containing the connecting port the client wants to connect to on the server and the outside IP address it was originally addressed to. This could have been also been retrieved from the IP header, but that header could be changed by a NAT router.

position length field encrypted description
0 4 IP address no outside IP address of the server
4 4 key position no key used to encrypt this message
8 2 port yes port of service you want secured
10 4 nonce yes a couple of bytes of the key
14 20 message digest yes SHA1(port+nonce)
Figure 4: First message

position length field encrypted description
0 4 message length no length of the message
4 4 key position no key used to encrypt this message
8 4 nonce yes a couple of bytes of the key
12 ? message yes the message (or packet)
? 20 message digest yes SHA1(nonce+message)
Figure 5: Normal message

field why it is necessary
key position If a packet gets lost we must be able to resynchronize, therefore we send the number of the key of the first byte of this packet.
message digest This field is added to prevent the adjustment of known plaintext. It also prevents key deletion when people send random data to the server.
nonce We add a nonce to prevent that the attacker can calculate the message digest once he knows the entire plaintext.
message length Because we add length to the packet it may be fragmented by the IP layer and therefore we cannot rely on the IP packet size.
Figure 6: Fields explained

After a "first" message the client sends "normal" messages for every packet it receives. As you can see in figure 5 we've added a few fields. In figure 6 you can read why we did this.

When a message is encrypted or decrypted we delete the key to accomplish perfect forward secrecy. Every packet is encoded with the last bytes of the corresponding key file (one for upstream and one for downstream traffic). After we used the key we truncate the file. We know that we should write zero's to the disk three times to make sure the data is really gone, but we didn't do it, although we know it would improve security.

So this scheme might seem secure, but beware of side channel attacks! The messages should be padded with a random number of zero's. Even better would be to pad every packet up to the maximum packet size. To even improve that scheme we should send some messages now and then that contain only padding. We didn't implement it, although we know it would improve security.

5.2.2 How to use otptun

Because otptun runs as a user program, we were able to do a quick implementation of it. We hope we implemented it without any bugs and that it contains no protocol failures.

computer syntax
server ruby otptund.rb
client ruby otptun.rb client_ip server_ip port
keygen ruby otptunkg.rb client_ip server_ip size1 size2
Table 1: Syntax

Let's explain how you can use this software. In table 1 you can find the syntax for the server and the client software. In our example below two computers are connected to a LAN. The server has IP address 192.168.1.1 and the client has IP address 192.168.1.2. In table 2 you can find the steps you have to take to run the software. Your IP addresses will probably be different, replace them in the instructions. Make sure you use the outside IP addresses if you are behind a NAT router.

step
description
1
The environments of the computers you are going to run the software on are "trusted". This means we assume that malicious software is not and can not be installed and that users of the system can be trusted. Run a trojan, rootkit and virus scanner to make sure you are "clean" and enable your firewall.
2

Create the keys for both computers using the keygen
"ruby otptunkg.rb 192.168.1.1 192.168.1.2 1M 2M"

3
Transfer the keyfiles from this machine to the other machine through a "secure channel" (e.g. an usb stick)
4
Make sure that the SSH server is running (on port 22) on the server machine
5
Start the server on that machine using the command
"ruby otptund.rb"
6
Make sure the firewall of the server doesn't block incoming TCP traffic on port 1027
7
Start the client on the other machine using the command
"ruby otptun.rb 192.168.1.1 192.168.1.2 22"
8
Connect the SSH client to localhost port 1027 using
"ssh -p 1027 127.0.0.1"
9
Use SSH as you normally would
Table 1: Operation instructions

Note that "otptun" requires only one open port in your firewall and that both the client and server may run behind a NAT router (as long as you forward port 1027 on the server side).

5.2.3 Known issues

imac-g5:~/otptun mjb$ scp -P 1027 10mb.bin root@127.0.0.1:/root
root@127.0.0.1's password:
10mb.bin                                      100%   10MB 243.8KB/s   00:42
imac-g5:~/otptun mjb$ scp 10mb.bin root@192.168.1.11:/root
root@192.168.1.11's password:
10mb.bin                                      100%   10MB  10.0MB/s   00:01
imac-g5:~/otptun mjb$
Figure 6: Copying a 10 megabyte file over a 100mbit connection first using and then not using otptun.

The following issues are known:

5.2.4 Disclaimer

The source code for otptun can be found in Appendix A. It is written in Ruby and is as minimal as possible to make it easy to read. This implementation is not intended for actual use, it works, but is by no means finished, it has no error handling, is not user-friendly and performs terrible. It is only written as a proof-of-concept and may still contain bugs and/or protocol failures. Do NOT use it to encrypt communication that needs to be secured.

6 Conclusion

We've explained how one-time pad encryption works, who invented it and who proved it to be secure. Then we described the requirements of a good crypto system. When we compare one-time pad to public key crypto systems we immediately notice the absence of the brute force cracking method in one-time pad systems. But this absence comes with a high price: it introduces a complex key distribution problem. Apart from that one-time pad has the same advantages and disadvantages that any stream cipher has.

Although real life applications are limited, we feel that it is an underestimated technology that can, in some cases, be applied with success. The advantages are: high speed, low complexity and high security. The disadvantages: complex key distribution and the need for large true random keys.

When you have just a few parties to communicate with, you need a high performance and you don't want your messages ever to be readable (by someone without a key), it might be the right choice.

7 Bibliography

  1. Key Management: Generation, Verification, and Distribution, mils electronic gesmbh & cokg
    http://www.mils.com/pdf/TEC-KeyMan-01E-01-H.pdf
  2. Message Security: Cryptographic Algorithms, mils electronic gesmbh & cokg
    http://www.mils.com/pdf/TEC-ALG-03E-01-H.pdf
  3. Weaknesses in the Key Scheduling Algorithm of RC4, Fluhrer, Mantin and Shamir
    http://www.drizzle.com/~aboba/IEEE/rc4_ksaproc.pdf
  4. Statistical Analysis of the Alleged RC4 Keystream Generator, 2002, Fluhrer and McGrew
    http://www.wisdom.weizmann.ac.il/~itsik/RC4/Papers/FluhrerMcgrew.pdf
  5. Std. 802.11 Part 11: Wireless LAN MAC and PHY Specifications, 2003, ANSIE/IEEE
    http://standards.ieee.org/getieee802/download/802.11-1999.pdf
  6. Re-visiting the One-Time Pad ,Aug 2005, N. Nagaraj, V. Vaidya, P. G. Vaidya
    http://arxiv.org/abs/cs.CR/0508079
  7. The Final Nail in WEP’s Coffin, 2006, Andrea Bittau, Mark Handley, Joshua Lackey http://www.cs.ucl.ac.uk/staff/M.Handley/papers/fragmentation.pdf
  8. Data communication - High-level data link control procedures - Frame structure.
    ISO 3309
  9. Communication Theory of Secrecy Systems, 1949, C. E. Shannon
    http://netlab.cs.ucla.edu/wiki/files/shannon1949.pdf
  10. Amendment 6: Medium Access Control (MAC) Security Enhancements, 2003, IEEE
    http://standards.ieee.org/getieee802/download/802.11i-2004.pdf
  11. Network Security, 2002, Charlie Kaufman, Radia Perlman and Mike Spencer
    chapter 5.2.3.2 Mixing in the plaintext p. 126
  12. Crypto-Gram Newsletter, October 2002, Bruce Schneier
    http://www.schneier.com/crypto-gram-0210.html#7
  13. TrueCrypt website
    http://www.truecrypt.org/
  14. HG400 Random Number Generators, 2005, Random
    http://random.com.hr/products/hg400/index.html
  15. Crypto class notes from user 'stinson', Stanford University, 2003
    http://www.stanford.edu/~stinson/crypto/S3995/class_4.txt, line 303

Appendix A

otptunkg.rb

#!/usr/bin/ruby
# read arguments
clientoutsideip = ARGV.shift
serveroutsideip = ARGV.shift
port = ARGV.shift
size1 = ARGV.shift.downcase
size2 = ARGV.shift.downcase
# convert string to number of bytes
def numBytes(size)
  if size.match(/^[0-9]+(g|k|m|b)$/)
    count = size[0...-1].to_i
    multiplier = size[-1,1]
    case multiplier
      when 'g' then return count*1024*1024*1024
      when 'm' then return count*1024*1024
      when 'k' then return count*1024
      when 'b' then return count
    end
  end 
  return size.to_i
end
# generate keys
random = File.open("/dev/random","r")
key1 = File.new("#{clientoutsideip}.#{serveroutsideip}.#{port}.otpkey", "w")
key1.write(random.read(numBytes(size1)))
key1.close
key2 = File.new("#{serveroutsideip}.#{clientoutsideip}.#{port}.otpkey", "w")
key2.write(random.read(numBytes(size2)))
key2.close
random.close

otp.rb

require 'digest/sha1'

$OTPTUNPORT = 1027 # otptund port
$LOCALHOST = '127.0.0.1'

def readnonce(keyfile,keyposition)
  raise('out of key error') if keyposition<0
  raise('key sync error') if keyposition+4>keyfile.stat.size
  keyfile.pos=keyposition
  return keyfile.read(4)
end

def crypt(keyfile,keyposition,ciphertext)
  raise('out of key error') if keyposition<0
  raise('key sync error') if keyposition+ciphertext.length>keyfile.stat.size
  keyfile.pos=keyposition
  key = keyfile.read(ciphertext.length)
  (0..ciphertext.length-1).each { |i| 
    ciphertext[i] ^= key[i]
  }
  return ciphertext
end

def openFiles(ipaddress1,ipaddress2)
  filename1 = ipaddress1.unpack('CCCC').join('.')
  filename2 = ipaddress2.unpack('CCCC').join('.')
  key1filename = filename2+'.'+filename1+'.otpkey'
  keyfile1 = File.open(key1filename,'r+')
  raise("cant open keyfile #{key1filename}") if keyfile1==nil
  key2filename = filename1+'.'+filename2+'.otpkey'
  keyfile2 = File.open(key2filename,'r+')
  raise("cant open keyfile #{key2filename}") if keyfile2==nil
  return [keyfile1,keyfile2]
end

def receiveFirst(socket)
  length = 34
  data = socket.recv(length)
  raise('client disconnected') if data==''
  raise('invalid first packet size') if data.length!=length
  ipaddress,keyposition,ciphertext = data.unpack('a4Na26')
  clientaddress = socket.peeraddr[3].split('.')
  clientaddress.collect! { |n| n.to_i.chr }
  clientaddress = clientaddress.join
  decryptkeyfile,encryptkeyfile = openFiles(ipaddress,clientaddress)
  noncecheck = readnonce(decryptkeyfile,keyposition)
  raise('nonce read failed') if noncecheck==nil
  plaintext = crypt(decryptkeyfile,keyposition+4,ciphertext)
  raise('decryption failed') if plaintext==nil
  serviceport, nonce, messagedigest = plaintext.unpack('na4a20')
  messagedigestcheck = Digest::SHA1.digest([serviceport].pack('n')+nonce)
  raise('nonce check failed') if nonce != noncecheck
  raise('digest check failed') if messagedigest != messagedigestcheck
  decryptkeyfile.truncate(keyposition)
  begin
    t = TCPSocket.new($LOCALHOST,serviceport)
  rescue
    raise("cant connect to #{$LOCALHOST}:#{serviceport}") 
  end
  return [decryptkeyfile,encryptkeyfile,t]
end

def sendFirst(socket,outsideip,remotehost,serviceport)
  decryptkeyfile,encryptkeyfile = openFiles(outsideip,remotehost)
  length = 34
  keyposition = encryptkeyfile.stat.size-30
  data = [remotehost,keyposition].pack('a4N')
  nonce = readnonce(encryptkeyfile,keyposition)
  messagedigest = Digest::SHA1.digest([serviceport].pack('n')+nonce)
  plaintext = [serviceport].pack('n')+nonce+messagedigest
  ciphertext=crypt(encryptkeyfile,keyposition+4,plaintext)
  data += ciphertext
  remoteip = remotehost.unpack('CCCC').join('.')
  begin
    t = TCPSocket.new(remoteip,$OTPTUNPORT)
  rescue
    raise("cant connect to #{remoteip}:#{$OTPTUNPORT}") 
  end
  begin
    t.send(data,0)
  rescue
    raise("cant send") 
  end
  encryptkeyfile.truncate(keyposition)
  return [decryptkeyfile,encryptkeyfile,t]
end

def receive(socket,decryptkeyfile)
  data = socket.recv(4)
  raise('client disconnected') if data==''
  length = data.unpack('N')[0]+28
  data = ''
  while data.length<length do
    newdata=socket.recv(length-data.length)
    raise('client disconnected') if newdata==''
    data+=newdata
  end
  keyposition,ciphertext = data.unpack('Na*')
  noncecheck = readnonce(decryptkeyfile,keyposition)
  raise('nonce read failed') if noncecheck==nil
  plaintext = crypt(decryptkeyfile,keyposition+4,ciphertext)
  raise('decryption failed') if plaintext==nil
  nonce,message,messagedigest = plaintext.unpack("a4a#{plaintext.length-24}a20")
  messagedigestcheck = Digest::SHA1.digest(nonce+message)
  raise('nonce check failed') if nonce != noncecheck
  raise('digest check failed')if messagedigest != messagedigestcheck
  decryptkeyfile.truncate(keyposition)
  return message
end

def send(socket,encryptkeyfile,message)
  length = message.length+28
  keyposition = encryptkeyfile.stat.size-length
  data = [message.length,keyposition].pack('NN')
  nonce = readnonce(encryptkeyfile,keyposition)
  messagedigest = Digest::SHA1.digest(nonce+message)
  plaintext = nonce+message+messagedigest
  ciphertext=crypt(encryptkeyfile,keyposition+4,plaintext)
  data += ciphertext
  begin
    socket.send(data,0)
  rescue
    raise("cant send") 
  end
  encryptkeyfile.truncate(keyposition)
end

otptun.rb

#some code soon
#!/usr/bin/ruby

require 'socket'
require 'otp'

outsideip = ARGV.shift
outsideip = outsideip.split('.')
outsideip.collect! { |n| n.to_i.chr }
outsideip = outsideip.join
remotehost = ARGV.shift
remotehost = remotehost.split('.')
remotehost.collect! { |n| n.to_i.chr }
remotehost = remotehost.join

serviceport = ARGV.shift.to_i or 23

server = TCPServer.open($LOCALHOST,$OTPTUNPORT)

port = server.addr[1]
addrs = server.addr[2..-1].uniq

puts "*** listening on #{addrs.collect{|a|"#{a}:#{port}"}.join(' ')}"

loop do
  socket = server.accept
  
  #Thread.start(socket) do |s| # one thread per client
    s=socket

    port = s.peeraddr[1]
    name = s.peeraddr[2]
    addr = s.peeraddr[3]
    
    puts "*** connected from #{addr}:#{port}"

    begin
      decryptkeyfile,encryptkeyfile,t = sendFirst(s,outsideip,remotehost,serviceport)

      while true 
      sel=select([s,t])
      if sel!=nil
        # Iterate through the tagged read descriptors
        for sock in sel[0]
          if sock==t
            packet = receive(t,decryptkeyfile)
	           raise('client disconnected') if packet==nil
            #puts "send packet #{packet.length} bytes"
            s.send(packet,0)
          elsif sock==s
            packet = s.recv(65536)
            #puts "received packet #{packet.length} bytes"
            raise('client disconnected') if packet==''
            send(t,encryptkeyfile,packet)
          end
        end
      end
    end

    rescue RuntimeError

    ensure
      # close socket on error
      decryptkeyfile.close if decryptkeyfile
      encryptkeyfile.close if encryptkeyfile
      t.close if t
      s.close if s 
    end

    puts "*** #{addr}:#{port} disconnected"
  #end

end

otptund.rb

#!/usr/bin/ruby

require 'socket'
require 'otp'

server = TCPServer.open($OTPTUNPORT)

port = server.addr[1]
addrs = server.addr[2..-1].uniq

puts "*** listening on #{addrs.collect{|a|"#{a}:#{port}"}.join(' ')}"

loop do
  socket = server.accept
  
  #Thread.start(socket) do |s| # one thread per client
    s=socket

    port = s.peeraddr[1]
    name = s.peeraddr[2]
    addr = s.peeraddr[3]
    
    puts "*** connected from #{addr}:#{port}"

    begin
      select([s])
      decryptkeyfile,encryptkeyfile,t = receiveFirst(s)

      while true 
      sel=select([s,t])
      if sel!=nil
        # Iterate through the tagged read descriptors
        for sock in sel[0]
          if sock==s
            packet = receive(s,decryptkeyfile)
            raise('client disconnected') if packet==nil
            #puts "send packet #{packet.length} bytes"
            t.send(packet,0)
          elsif sock==t
            packet = t.recv(65536)
            raise('client disconnected') if packet==''
            #puts "received packet #{packet.length} bytes (#{packet})"
            send(s,encryptkeyfile,packet)
          end
        end
      end
    end

    rescue RuntimeError

    ensure
      # close socket on error
      decryptkeyfile.close if decryptkeyfile
      encryptkeyfile.close if encryptkeyfile
      t.close if t
      s.close if s 
    end

    puts "*** #{addr}:#{port} disconnected"
  #end

end