Skip to content
This repository has been archived by the owner on Oct 31, 2023. It is now read-only.

Commit

Permalink
Merge pull request #1 from vapor/first
Browse files Browse the repository at this point in the history
First
  • Loading branch information
tanner0101 authored Mar 7, 2017
2 parents 87f6ed1 + 1b8440a commit 629048a
Show file tree
Hide file tree
Showing 15 changed files with 1,041 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
Package.pins

11 changes: 11 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// swift-tools-version:3.1

import PackageDescription

let package = Package(
name: "BCrypt",
dependencies: [
// Module for generating random bytes and numbers.
.Package(url: "https://github.com/vapor/random.git", majorVersion: 0)
]
)
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,40 @@
# bcrypt

![Swift](http://img.shields.io/badge/swift-3.1-brightgreen.svg)
[![CircleCI](https://circleci.com/gh/vapor/bcrypt.svg?style=shield)](https://circleci.com/gh/vapor/bcrypt)
[![Slack Status](http://vapor.team/badge.svg)](http://vapor.team)

Swift implementation of the BCrypt password hashing function used in [Vapor](https://github.com/vapor/vapor)'s packages.

## Usage

### Hash

```swift
import BCrypt

let digest = try BCrypt.Hash.make(message: "foo")
print(digest.string)
```

### Verify

```swift
import BCrypt

let digest = "$2a$04$TI13sbmh3IHnmRepeEFoJOkVZWsn5S1O8QOwm8ZU5gNIpJog9pXZm"
let result = try BCrypt.Hash.verify(message: "vapor", matches: digest)
print(result)
```

## 📖 Documentation

Visit the Vapor web framework's [documentation](http://docs.vapor.codes) for instructions on how to use this package.

## 💧 Community

Join the welcoming community of fellow Vapor developers in [slack](http://vapor.team).

## 🔧 Compatibility

This package has been tested on macOS and Ubuntu.
41 changes: 41 additions & 0 deletions Sources/BCrypt/BCryptError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
public enum BCryptError: String, Error {
case invalidHash
case invalidSaltByteCount
case invalidSaltVersion
case invalidSaltCost
case unsupportedSaltVersion
}

import Debugging

extension BCryptError: Debuggable {
public var reason: String {
switch self {
case .invalidHash:
return "The hash being parsed does not match the recognized format"
case .invalidSaltByteCount:
return "BCrypt salt requires 16 bytes"
case .invalidSaltVersion:
return "Invalid salt version format"
case .invalidSaltCost:
return "Invalid salt cost format"
case .unsupportedSaltVersion:
return "Unsupported salt version"
}
}

public var identifier: String {
return rawValue
}

public var possibleCauses: [String] {
return [
"BCrypt hash being parsed is not in the format `$2x$xx$ssssssssssssssssssssssddddddddddddddddddddddddddddddd`",
"BCrypt hash is not base64 encoded properly"
]
}

public var suggestedFixes: [String] {
return []
}
}
140 changes: 140 additions & 0 deletions Sources/BCrypt/Base64.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import Core

/// Base64 extension for BCrypt. This is a weird base64 since instead of using
/// /+ for the last two characters, or the urlEncoded -_, it uses /.
struct Base64 {
static let encodingTable : [Byte] = [
.period, .forwardSlash, .A, .B, .C, .D, .E, .F, .G, .H, .I, .J, .K,
.L, .M, .N, .O, .P, .Q, .R, .S, .T, .U, .V, .W, .X,
.Y, .Z, .a, .b, .c, .d, .e, .f, .g, .h, .i, .j, .k,
.l, .m, .n, .o, .p, .q, .r, .s, .t, .u, .v, .w, .x,
.y, .z, .zero, .one, .two, .three, .four, .five, .six, .seven, .eight, .nine
]

static let decodingTable : [Byte] = [
.max, .max, .max, .max, .max, .max, .max, .max, .max, .max,
.max, .max, .max, .max, .max, .max, .max, .max, .max, .max,
.max, .max, .max, .max, .max, .max, .max, .max, .max, .max,
.max, .max, .max, .max, .max, .max, .max, .max, .max, .max,
.max, .max, .max, .max, .max, .max, 0, 1, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, .max, .max,
.max, .max, .max, .max, .max, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, .max, .max, .max, .max, .max, .max, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, .max, .max, .max, .max, .max
]

static func encode(_ bytes: Bytes, count: UInt) -> Bytes {
if bytes.count == 0 || count == 0 {
return []
}

var len: Int = Int(count)
if len > bytes.count {
len = bytes.count
}

var offset: Int = 0
var c1: UInt8
var c2: UInt8
var result: Bytes = []

while offset < len {
c1 = bytes[offset] & 0xff
offset += 1
result.append(encodingTable[Int((c1 >> 2) & 0x3f)])
c1 = (c1 & 0x03) << 4
if offset >= len {
result.append(encodingTable[Int(c1 & 0x3f)])
break
}

c2 = bytes[offset] & 0xff
offset += 1
c1 |= (c2 >> 4) & 0x0f
result.append(encodingTable[Int(c1 & 0x3f)])
c1 = (c2 & 0x0f) << 2
if offset >= len {
result.append(encodingTable[Int(c1 & 0x3f)])
break
}

c2 = bytes[offset] & 0xff
offset += 1
c1 |= (c2 >> 6) & 0x03
result.append(encodingTable[Int(c1 & 0x3f)])
result.append(encodingTable[Int(c2 & 0x3f)])
}

return result
}

private static func char64of(x: Byte) -> Byte {
if x < 0 || x > 128 - 1 {
// The character would go out of bounds of the pre-calculated array so return -1.
return Byte.max
}

// Return the matching Base64 encoded character.
return decodingTable[Int(x)]
}

static func decode(_ s: Bytes, count maxolen: UInt) -> Bytes {
let maxolen = Int(maxolen)

var off: Int = 0
var olen: Int = 0
var result = Bytes(repeating: 0, count: maxolen)

var c1: Byte
var c2: Byte
var c3: Byte
var c4: Byte
var o: Byte

while off < s.count - 1 && olen < maxolen {
c1 = char64of(x: s[off])
off += 1
c2 = char64of(x: s[off])
off += 1
if c1 == Byte.max || c2 == Byte.max {
break
}

o = c1 << 2
o |= (c2 & 0x30) >> 4
result[olen] = o
olen += 1
if olen >= maxolen || off >= s.count {
break
}

c3 = char64of(x: s[Int(off)])
off += 1

if c3 == Byte.max {
break
}

o = (c2 & 0x0f) << 4
o |= (c3 & 0x3c) >> 2
result[olen] = o
olen += 1
if olen >= maxolen || off >= s.count {
break
}

c4 = char64of(x: s[off])
off += 1
o = (c3 & 0x03) << 6
o |= c4
result[olen] = o
olen += 1
}

return result[0..<olen].array
}
}
52 changes: 52 additions & 0 deletions Sources/BCrypt/Convenience.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Core

// MARK: Serializer

extension Hash {
public static func make(message: Bytes, with salt: Salt? = nil) throws -> Bytes {
let hash = try Hash(salt)
let digest = hash.digest(message: message)
let serializer = Serializer(hash.salt, digest: digest)
return serializer.serialize()
}

public static func make(message: BytesConvertible, with salt: Salt? = nil) throws -> Bytes {
return try make(
message: message.makeBytes(),
with: salt
)
}
}

// MARK: Parser

extension Hash {
public static func verify(message: Bytes, matches input: Bytes) throws -> Bool {
let parser = try Parser(input)
let salt = try parser.parseSalt()
let hasher = try Hash(salt)
let testDigest = hasher.digest(message: message)
return try testDigest == parser.parseDigest() ?? []
}

public static func verify(message: BytesConvertible, matches digest: BytesConvertible) throws -> Bool {
return try verify(
message: message.makeBytes(),
matches: digest.makeBytes()
)
}

public static func verify(message: Bytes, matches digest: BytesConvertible) throws -> Bool {
return try verify(
message: message,
matches: digest.makeBytes()
)
}

public static func verify(message: BytesConvertible, matches digest: Bytes) throws -> Bool {
return try verify(
message: message.makeBytes(),
matches: digest
)
}
}
1 change: 1 addition & 0 deletions Sources/BCrypt/Export.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@_exported import Core
Loading

0 comments on commit 629048a

Please sign in to comment.