Skip to content

Commit

Permalink
add missing files to complete integration of KeeTrayOTP, fixes #43 and
Browse files Browse the repository at this point in the history
  • Loading branch information
Philipp Crocoll committed Oct 24, 2017
1 parent e491463 commit 6eee282
Show file tree
Hide file tree
Showing 4 changed files with 594 additions and 0 deletions.
132 changes: 132 additions & 0 deletions src/keepass2android/Totp/Base32.cs
@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace KeeTrayTOTP.Libraries
{
/// <summary>
/// Utility to deal with Base32 encoding and decoding.
/// </summary>
/// <remarks>
/// http://tools.ietf.org/html/rfc4648
/// </remarks>
public static class Base32
{
/// <summary>
/// The number of bits in a base32 encoded character.
/// </summary>
private const int encodedBitCount = 5;
/// <summary>
/// The number of bits in a byte.
/// </summary>
private const int byteBitCount = 8;
/// <summary>
/// A string containing all of the base32 characters in order.
/// This allows a simple indexof or [index] to convert between
/// a numeric value and an encoded character and vice versa.
/// </summary>
private const string encodingChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
/// <summary>
/// Takes a block of data and converts it to a base 32 encoded string.
/// </summary>
/// <param name="data">Input data.</param>
/// <returns>base 32 string.</returns>
public static string Encode(byte[] data)
{
if (data == null)
throw new ArgumentNullException();
if (data.Length == 0)
throw new ArgumentNullException();

// The output character count is calculated in 40 bit blocks. That is because the least
// common blocks size for both binary (8 bit) and base 32 (5 bit) is 40. Padding must be used
// to fill in the difference.
int outputCharacterCount = (int)Math.Ceiling(data.Length / (decimal)encodedBitCount) * byteBitCount;
char[] outputBuffer = new char[outputCharacterCount];

byte workingValue = 0;
short remainingBits = encodedBitCount;
int currentPosition = 0;

foreach (byte workingByte in data)
{
workingValue = (byte)(workingValue | (workingByte >> (byteBitCount - remainingBits)));
outputBuffer[currentPosition++] = encodingChars[workingValue];

if (remainingBits <= byteBitCount - encodedBitCount)
{
workingValue = (byte)((workingByte >> (byteBitCount - encodedBitCount - remainingBits)) & 31);
outputBuffer[currentPosition++] = encodingChars[workingValue];
remainingBits += encodedBitCount;
}

remainingBits -= byteBitCount - encodedBitCount;
workingValue = (byte)((workingByte << remainingBits) & 31);
}

// If we didn't finish, write the last current working char.
if (currentPosition != outputCharacterCount)
outputBuffer[currentPosition++] = encodingChars[workingValue];

// RFC 4648 specifies that padding up to the end of the next 40 bit block must be provided
// Since the outputCharacterCount does account for the paddingCharacters, fill it out.
while (currentPosition < outputCharacterCount)
{
// The RFC defined paddinc char is '='.
outputBuffer[currentPosition++] = '=';
}

return new string(outputBuffer);
}

/// <summary>
/// Takes a base 32 encoded value and converts it back to binary data.
/// </summary>
/// <param name="base32">Base 32 encoded string.</param>
/// <returns>Binary data.</returns>
public static byte[] Decode(string base32)
{
if (string.IsNullOrEmpty(base32))
throw new ArgumentNullException();

var unpaddedBase32 = base32.ToUpperInvariant().TrimEnd('=');
foreach (var c in unpaddedBase32)
{
if (encodingChars.IndexOf(c) < 0)
throw new ArgumentException("Base32 contains illegal characters.");
}

// we have already removed the padding so this will tell us how many actual bytes there should be.
int outputByteCount = unpaddedBase32.Length * encodedBitCount / byteBitCount;
byte[] outputBuffer = new byte[outputByteCount];

byte workingByte = 0;
short bitsRemaining = byteBitCount;
int mask = 0;
int arrayIndex = 0;

foreach (char workingChar in unpaddedBase32)
{
int encodedCharacterNumericValue = encodingChars.IndexOf(workingChar);

if (bitsRemaining > encodedBitCount)
{
mask = encodedCharacterNumericValue << (bitsRemaining - encodedBitCount);
workingByte = (byte)(workingByte | mask);
bitsRemaining -= encodedBitCount;
}
else
{
mask = encodedCharacterNumericValue >> (encodedBitCount - bitsRemaining);
workingByte = (byte)(workingByte | mask);
outputBuffer[arrayIndex++] = workingByte;
workingByte = (byte)(encodedCharacterNumericValue << (byteBitCount - encodedBitCount + bitsRemaining));
bitsRemaining += byteBitCount - encodedBitCount;
}
}

return outputBuffer;
}
}
}
50 changes: 50 additions & 0 deletions src/keepass2android/Totp/TOTPEncoder.cs
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace KeeTrayTOTP.Libraries
{
class TOTPEncoder
{
/// <summary>
/// Character set for authenticator code
/// </summary>
private static readonly char[] STEAMCHARS = new char[] {
'2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C',
'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q',
'R', 'T', 'V', 'W', 'X', 'Y'};


private static uint OTP2UInt(byte[] totp)
{
uint fullcode = BitConverter.ToUInt32(totp, 0) & 0x7fffffff;

return fullcode;
}

public static readonly Func<byte[], int, string> rfc6238 = (byte[] bytes, int length) =>
{
uint fullcode = TOTPEncoder.OTP2UInt(bytes);
uint mask = (uint)Math.Pow(10, length);
return (fullcode % mask).ToString(new string('0', length));
};

public static readonly Func<byte[], int, string> steam = (byte[] bytes, int length) =>
{
uint fullcode = TOTPEncoder.OTP2UInt(bytes);
StringBuilder code = new StringBuilder();
for (var i = 0; i < length; i++)
{
code.Append(STEAMCHARS[fullcode % STEAMCHARS.Length]);
fullcode /= (uint)STEAMCHARS.Length;
}
return code.ToString();
};


}
}

0 comments on commit 6eee282

Please sign in to comment.