diff options
-rw-r--r-- | TwoStepsAuthenticator.TestApp/App.config | 6 | ||||
-rw-r--r-- | TwoStepsAuthenticator.TestApp/Program.cs | 17 | ||||
-rw-r--r-- | TwoStepsAuthenticator.TestApp/Properties/AssemblyInfo.cs | 36 | ||||
-rw-r--r-- | TwoStepsAuthenticator.TestApp/TwoStepsAuthenticator.TestApp.csproj | 64 | ||||
-rw-r--r-- | TwoStepsAuthenticator.TestWebsite.sln | 6 | ||||
-rw-r--r-- | TwoStepsAuthenticator.TestWebsite/TwoStepsAuthenticator.TestWebsite.csproj | 1 | ||||
-rw-r--r-- | TwoStepsAuthenticator.TestWebsite/default.html | 199 | ||||
-rw-r--r-- | TwoStepsAuthenticator/Base32Encoding.cs | 134 | ||||
-rw-r--r-- | TwoStepsAuthenticator/TwoStepsAuthenticator.cs | 30 | ||||
-rw-r--r-- | TwoStepsAuthenticator/TwoStepsAuthenticator.csproj | 1 |
10 files changed, 491 insertions, 3 deletions
diff --git a/TwoStepsAuthenticator.TestApp/App.config b/TwoStepsAuthenticator.TestApp/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/TwoStepsAuthenticator.TestApp/App.config @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8" ?> +<configuration> + <startup> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> + </startup> +</configuration>
\ No newline at end of file diff --git a/TwoStepsAuthenticator.TestApp/Program.cs b/TwoStepsAuthenticator.TestApp/Program.cs new file mode 100644 index 0000000..6aa9a3a --- /dev/null +++ b/TwoStepsAuthenticator.TestApp/Program.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TwoStepsAuthenticator.TestApp +{ + class Program + { + static void Main(string[] args) + { + var auth = new TwoStepsAuthenticator(); + auth.GetPinCode("JBSWY3DPEHPK3PXP", new DateTime(2013, 6, 12, 18, 0, 0)); + } + } +} diff --git a/TwoStepsAuthenticator.TestApp/Properties/AssemblyInfo.cs b/TwoStepsAuthenticator.TestApp/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..780e009 --- /dev/null +++ b/TwoStepsAuthenticator.TestApp/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TwoStepsAuthenticator.TestApp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TwoStepsAuthenticator.TestApp")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("76a40173-f28a-4d49-80b9-3d25353ab795")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TwoStepsAuthenticator.TestApp/TwoStepsAuthenticator.TestApp.csproj b/TwoStepsAuthenticator.TestApp/TwoStepsAuthenticator.TestApp.csproj new file mode 100644 index 0000000..31b0300 --- /dev/null +++ b/TwoStepsAuthenticator.TestApp/TwoStepsAuthenticator.TestApp.csproj @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{D14B3558-877D-4219-817F-F61670A62A26}</ProjectGuid> + <OutputType>Exe</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>TwoStepsAuthenticator.TestApp</RootNamespace> + <AssemblyName>TwoStepsAuthenticator.TestApp</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Program.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="App.config" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\TwoStepsAuthenticator\TwoStepsAuthenticator.csproj"> + <Project>{6c898cd1-0bf3-4711-847e-ad7dac815cd8}</Project> + <Name>TwoStepsAuthenticator</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/TwoStepsAuthenticator.TestWebsite.sln b/TwoStepsAuthenticator.TestWebsite.sln index 2cf0269..1f71921 100644 --- a/TwoStepsAuthenticator.TestWebsite.sln +++ b/TwoStepsAuthenticator.TestWebsite.sln @@ -5,6 +5,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwoStepsAuthenticator.TestW EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwoStepsAuthenticator", "TwoStepsAuthenticator\TwoStepsAuthenticator.csproj", "{6C898CD1-0BF3-4711-847E-AD7DAC815CD8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwoStepsAuthenticator.TestApp", "TwoStepsAuthenticator.TestApp\TwoStepsAuthenticator.TestApp.csproj", "{D14B3558-877D-4219-817F-F61670A62A26}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -19,6 +21,10 @@ Global {6C898CD1-0BF3-4711-847E-AD7DAC815CD8}.Debug|Any CPU.Build.0 = Debug|Any CPU {6C898CD1-0BF3-4711-847E-AD7DAC815CD8}.Release|Any CPU.ActiveCfg = Release|Any CPU {6C898CD1-0BF3-4711-847E-AD7DAC815CD8}.Release|Any CPU.Build.0 = Release|Any CPU + {D14B3558-877D-4219-817F-F61670A62A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D14B3558-877D-4219-817F-F61670A62A26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D14B3558-877D-4219-817F-F61670A62A26}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D14B3558-877D-4219-817F-F61670A62A26}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TwoStepsAuthenticator.TestWebsite/TwoStepsAuthenticator.TestWebsite.csproj b/TwoStepsAuthenticator.TestWebsite/TwoStepsAuthenticator.TestWebsite.csproj index 2495e1e..462f918 100644 --- a/TwoStepsAuthenticator.TestWebsite/TwoStepsAuthenticator.TestWebsite.csproj +++ b/TwoStepsAuthenticator.TestWebsite/TwoStepsAuthenticator.TestWebsite.csproj @@ -57,6 +57,7 @@ <Reference Include="System.EnterpriseServices" /> </ItemGroup> <ItemGroup> + <Content Include="default.html" /> <Content Include="Web.config" /> </ItemGroup> <ItemGroup> diff --git a/TwoStepsAuthenticator.TestWebsite/default.html b/TwoStepsAuthenticator.TestWebsite/default.html new file mode 100644 index 0000000..ad8522c --- /dev/null +++ b/TwoStepsAuthenticator.TestWebsite/default.html @@ -0,0 +1,199 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <title> - jsFiddle demo by russau</title> + + <script type='text/javascript' src='http://code.jquery.com/jquery-1.6.4.js'></script> + <link rel="stylesheet" type="text/css" href="/css/normalize.css"> + + + <link rel="stylesheet" type="text/css" href="/css/result-light.css"> + + + <link rel="stylesheet" type="text/css" href="http://twitter.github.com/bootstrap/1.3.0/bootstrap.css"> + + + + + + <script type='text/javascript' src="https://github.com/Caligatio/jsSHA/raw/master/src/sha.js"></script> + + + <style type='text/css'> + body { padding-top: 60px; } +.container-fluid { min-width: 100px } + + </style> + + + +<script type='text/javascript'>//<![CDATA[ + + function dec2hex(s) { return (s < 15.5 ? '0' : '') + Math.round(s).toString(16); } + function hex2dec(s) { return parseInt(s, 16); } + + function base32tohex(base32) { + var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + var bits = ""; + var hex = ""; + + for (var i = 0; i < base32.length; i++) { + var val = base32chars.indexOf(base32.charAt(i).toUpperCase()); + bits += leftpad(val.toString(2), 5, '0'); + } + + for (var i = 0; i + 4 <= bits.length; i += 4) { + var chunk = bits.substr(i, 4); + hex = hex + parseInt(chunk, 2).toString(16); + } + return hex; + + } + + function leftpad(str, len, pad) { + if (len + 1 >= str.length) { + str = Array(len + 1 - str.length).join(pad) + str; + } + return str; + } + + function updateOtp() { + + var key = base32tohex($('#secret').val()); + + //var date = new Date(); + var date = new Date(2013, 5, 12, 18, 0, 0, 0); //12 juin 2013 18h00 + + var epoch = Math.round(date.getTime() / 1000.0); + + + + var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0'); + + var hmacObj = new jsSHA(time, 'HEX'); + var hmac = hmacObj.getHMAC(key, 'HEX', 'SHA-1', "HEX"); + + $('#qrImg').attr('src', 'https://chart.googleapis.com/chart?chs=200x200&cht=qr&chl=200x200&chld=M|0&cht=qr&chl=otpauth://totp/user@host.com%3Fsecret%3D' + $('#secret').val()); + $('#secretHex').text(key); + $('#secretHexLength').text((key.length * 4) + ' bits'); + $('#epoch').text(time); + $('#hmac').empty(); + + if (hmac == 'KEY MUST BE IN BYTE INCREMENTS') { + $('#hmac').append($('<span/>').addClass('label important').append(hmac)); + } else { + var offset = hex2dec(hmac.substring(hmac.length - 1)); + var part1 = hmac.substr(0, offset * 2); + var part2 = hmac.substr(offset * 2, 8); + var part3 = hmac.substr(offset * 2 + 8, hmac.length - offset); + if (part1.length > 0) $('#hmac').append($('<span/>').addClass('label').append(part1)); + $('#hmac').append($('<span/>').addClass('label success').append(part2)); + if (part3.length > 0) $('#hmac').append($('<span/>').addClass('label').append(part3)); + } + + var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + ''; + otp = (otp).substr(otp.length - 6, 6); + + $('#otp').text(otp); + + var a = otp; + } + + function timer() { + var epoch = Math.round(new Date().getTime() / 1000.0); + var countDown = 30 - (epoch % 30); + if (epoch % 30 == 0) updateOtp(); + $('#updatingIn').text(countDown); + + } + + $(function () { + updateOtp(); + + $('#update').click(function (event) { + updateOtp(); + event.preventDefault(); + }); + + $('#secret').keyup(function () { + updateOtp(); + }); + + setInterval(timer, 1000); + }); + //]]> + +</script> + + +</head> +<body> + <div class="container-fluid"> + <div> + <div class="row"> + <div class="span8"> + <h1>Time-based One-time Password Algorithm</h1> + <p>This page contains a javascript implementation of the <em>Time-based One-time Password Algorithm</em> used by Google Authenticator and described in the <a href="http://tools.ietf.org/id/draft-mraihi-totp-timebased-06.html">TOTP RFC Draft</a>.</p> + + <p>Install Google Authenticator on your smartphone: <a href="http://itunes.apple.com/au/app/google-authenticator/id388497605?mt=8">iOS</a>, <a href="https://market.android.com/details?id=com.google.android.apps.authenticator&hl=en">Android</a>, <a href="http://m.google.com/authenticator">Blackberry</a>. As the TOTP is an open standard you can use this app to create one-time passwords for your own application. You add an account plus secret by scanning a QR code (more info on the <a href="http://code.google.com/p/google-authenticator/wiki/KeyUriFormat">google code wiki</a>). The javascript below implements the algorithm the smartphone app uses to generate the OTP - you would use this same algorithm <em>server-side</em> to verify an OTP.</p> + + <p>Put it to the test by setting the base32 secret, scanning the QR code in Google Authenticate. You should see the same OTP on your smartphone and displayed at the bottom on the page.</p> + + </div> + </div> + <div class="row"> + + <form> + <fieldset> + + <div class="clearfix"> + <label for="secret">Secret (base32)</label> + <div class="input"><input type="text" size="30" name="secret" id="secret" class="xlarge" value="JBSWY3DPEHPK3PXP" /></div> + </div> + <!-- /clearfix --> + <div class="clearfix"> + <label>Secret (hex)</label> + <div class="input"><span class="label" id="secretHex"></span> + <span id='secretHexLength'></span></div> + </div> + <!-- /clearfix --> + <div class="clearfix"> + <label>QR Code</label> + <div class="input"><img id="qrImg" src="" /></div> + </div> + <!-- /clearfix --> + <div class="clearfix"> + <label>Unix epoch div 30 (padded hex)</label> + <div class="input"><span class="label" id='epoch'></span></div> + </div> + <!-- /clearfix --> + <div class="clearfix"> + <label>HMAC(secret, time)</label> + <div class="input" id='hmac'></div> + </div> + <!-- /clearfix --> + <div class="clearfix"> + <label>One-time Password</label> + <div class="input"><span class="label success" id='otp'></span></div> + </div> + <!-- /clearfix --> + <div class="clearfix"> + <label>Updating in</label> + <div class="input"><span id='updatingIn'></span></div> + </div> + <!-- /clearfix --> + + + </fieldset> + </form> + </div> + </div> + </div> + + +</body> + + +</html> + diff --git a/TwoStepsAuthenticator/Base32Encoding.cs b/TwoStepsAuthenticator/Base32Encoding.cs new file mode 100644 index 0000000..40e2551 --- /dev/null +++ b/TwoStepsAuthenticator/Base32Encoding.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TwoStepsAuthenticator +{ + // This class was found just here : http://stackoverflow.com/questions/641361/base32-decoding + // No licence information, I think I'm allowed to use it here. + public class Base32Encoding + { + public static byte[] ToBytes(string input) + { + if (string.IsNullOrEmpty(input)) + { + throw new ArgumentNullException("input"); + } + + input = input.TrimEnd('='); //remove padding characters + int byteCount = input.Length * 5 / 8; //this must be TRUNCATED + byte[] returnArray = new byte[byteCount]; + + byte curByte = 0, bitsRemaining = 8; + int mask = 0, arrayIndex = 0; + + foreach (char c in input) + { + int cValue = CharToValue(c); + + if (bitsRemaining > 5) + { + mask = cValue << (bitsRemaining - 5); + curByte = (byte)(curByte | mask); + bitsRemaining -= 5; + } + else + { + mask = cValue >> (5 - bitsRemaining); + curByte = (byte)(curByte | mask); + returnArray[arrayIndex++] = curByte; + curByte = (byte)(cValue << (3 + bitsRemaining)); + bitsRemaining += 3; + } + } + + //if we didn't end with a full byte + if (arrayIndex != byteCount) + { + returnArray[arrayIndex] = curByte; + } + + return returnArray; + } + + public static string ToString(byte[] input) + { + if (input == null || input.Length == 0) + { + throw new ArgumentNullException("input"); + } + + int charCount = (int)Math.Ceiling(input.Length / 5d) * 8; + char[] returnArray = new char[charCount]; + + byte nextChar = 0, bitsRemaining = 5; + int arrayIndex = 0; + + foreach (byte b in input) + { + nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining))); + returnArray[arrayIndex++] = ValueToChar(nextChar); + + if (bitsRemaining < 4) + { + nextChar = (byte)((b >> (3 - bitsRemaining)) & 31); + returnArray[arrayIndex++] = ValueToChar(nextChar); + bitsRemaining += 5; + } + + bitsRemaining -= 3; + nextChar = (byte)((b << bitsRemaining) & 31); + } + + //if we didn't end with a full char + if (arrayIndex != charCount) + { + returnArray[arrayIndex++] = ValueToChar(nextChar); + while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding + } + + return new string(returnArray); + } + + private static int CharToValue(char c) + { + int value = (int)c; + + //65-90 == uppercase letters + if (value < 91 && value > 64) + { + return value - 65; + } + //50-55 == numbers 2-7 + if (value < 56 && value > 49) + { + return value - 24; + } + //97-122 == lowercase letters + if (value < 123 && value > 96) + { + return value - 97; + } + + throw new ArgumentException("Character is not a Base32 character.", "c"); + } + + private static char ValueToChar(byte b) + { + if (b < 26) + { + return (char)(b + 65); + } + + if (b < 32) + { + return (char)(b + 24); + } + + throw new ArgumentException("Byte is not a value Base32 value.", "b"); + } + + } +} diff --git a/TwoStepsAuthenticator/TwoStepsAuthenticator.cs b/TwoStepsAuthenticator/TwoStepsAuthenticator.cs index d298c96..d35feb7 100644 --- a/TwoStepsAuthenticator/TwoStepsAuthenticator.cs +++ b/TwoStepsAuthenticator/TwoStepsAuthenticator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; @@ -8,11 +9,34 @@ namespace TwoStepsAuthenticator { public class TwoStepsAuthenticator { - public void GetCode(string secret, DateTime date) - { - var seconds = Math.Floor(time.ToUnixTime() / 30); + //private static final int PASS_CODE_LENGTH = 6; + ///** Default passcode timeout period (in seconds) */ + //private static final int INTERVAL = 30; + HMACSHA1 mac; + + public void GetPinCode(string key, DateTime date) + { + TimeSpan ts = (date.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var interval = (long)ts.TotalSeconds / 30; + + var challengeBytes = BitConverter.GetBytes(interval); + // OK jusque là + + + mac = new HMACSHA1(Base32Encoding.ToBytes(key)); + var hash = mac.ComputeHash(challengeBytes); + //var hash = mac.TransformFinalBlock(challengeBytes); + //int offset = hash[hash.Length - 1] & 0xF; + //int truncatedHash = hashToInt(hash, offset) & 0x7FFFFFFF; + } + + + + private int hashToInt(byte[] bytes, int start) + { + return BitConverter.ToInt32(bytes, start); } } } diff --git a/TwoStepsAuthenticator/TwoStepsAuthenticator.csproj b/TwoStepsAuthenticator/TwoStepsAuthenticator.csproj index e0ecc9b..0f7341e 100644 --- a/TwoStepsAuthenticator/TwoStepsAuthenticator.csproj +++ b/TwoStepsAuthenticator/TwoStepsAuthenticator.csproj @@ -39,6 +39,7 @@ <Reference Include="System.Xml" /> </ItemGroup> <ItemGroup> + <Compile Include="Base32Encoding.cs" /> <Compile Include="TwoStepsAuthenticator.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="UnixTimeHelper.cs" /> |