Compare commits
6 commits
42c8250815
...
07d35d6684
Author | SHA1 | Date | |
---|---|---|---|
07d35d6684 | |||
80140fa867 | |||
b589296455 | |||
95ddaf0e18 | |||
d813622ed9 | |||
9834e850a5 |
6 changed files with 1533 additions and 337 deletions
5
Makefile
5
Makefile
|
@ -8,10 +8,7 @@ BINDIR = $(PREFIX)/bin
|
|||
MANDIR = $(PREFIX)/share/man/man1
|
||||
PKG_CONFIG = pkg-config
|
||||
|
||||
PKGS = libsodium
|
||||
LIBS != $(PKG_CONFIG) --libs $(PKGS)
|
||||
INCS != $(PKG_CONFIG) --cflags $(PKGS)
|
||||
CFLAGS += -std=c99 -pedantic -Wall -D_DEFAULT_SOURCE $(INCS)
|
||||
CFLAGS += -std=c99 -pedantic -Wall -D_DEFAULT_SOURCE
|
||||
|
||||
.c.o:
|
||||
$(CC) -o $@ $(CFLAGS) -c $<
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
# apm
|
||||
apm, argon password manager; a simple implementation of [pass](https://www.passwordstore.org/)(1) in C. It uses a unique key to encrypt every password, it provides functionality to edit, add, generate, show, list, remove passwords. It uses argon2 to create hash of master password and uses XSalsa20 to encrypt the password.
|
||||
|
||||
> The name "argon" is chosen as it uses argon2 algorithm and sodium(library) is stored with argon.
|
||||
A simple implementation of [pass](https://www.passwordstore.org/)(1) in C. It uses a unique key to encrypt every password, it provides functionality to edit, add, generate, show, list, remove passwords. It uses blake2b to create hash of master password and uses AES256-CBC to encrypt the password.
|
||||
|
||||
Before using apm, you must export 2 environment variables in order to make it work
|
||||
```sh
|
||||
|
@ -17,7 +15,7 @@ Usage: apm [-vhL] [[-e | -R | -I | -Q] <password>] [-M <file>] [-G <password> <l
|
|||
```
|
||||
|
||||
# Dependencies
|
||||
- libsodium
|
||||
None
|
||||
|
||||
# Building
|
||||
You will need to run these with elevated privilages.
|
||||
|
|
2
apm.1
2
apm.1
|
@ -14,7 +14,7 @@ apm \- Minimalistic password manager
|
|||
.RB [ \-G ]
|
||||
|
||||
.SH DESCRIPTION
|
||||
apm is a minimalistic command line password manager and a rewrite of pass in C. It uses a unique key to encrypt every password, it provides functionality to edit, add, generate, show, list, remove passwords. It uses argon2 to create hash of master password and uses XSalsa20 to encrypt the password.
|
||||
Simple implementation of pass in C. It uses a unique key to encrypt every password, it provides functionality to edit, add, generate, show, list, remove passwords. It uses blake2b to create hash of master password and uses AES256-CBC to encrypt the password.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
|
|
145
apm.c
145
apm.c
|
@ -1,26 +1,27 @@
|
|||
#include <dirent.h>
|
||||
#include <libgen.h>
|
||||
#include <linux/limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <libgen.h>
|
||||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/resource.h>
|
||||
#include <linux/limits.h>
|
||||
|
||||
#include <sodium.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "arg.h"
|
||||
|
||||
#define KEY_SIZE crypto_secretbox_KEYBYTES
|
||||
#define NONCE_SIZE crypto_secretbox_NONCEBYTES
|
||||
#define SALT_SIZE crypto_pwhash_SALTBYTES
|
||||
#include "aes256.h"
|
||||
#include "blake2b.h"
|
||||
|
||||
#define KEY_SIZE 32
|
||||
#define IV_SIZE 16
|
||||
|
||||
char *argv0;
|
||||
|
||||
void die(char *str);
|
||||
char *get_master_key(void);
|
||||
void random_bytes(uint8_t *bytes, size_t size);
|
||||
|
||||
void *memalloc(size_t size)
|
||||
{
|
||||
|
@ -143,7 +144,9 @@ char *get_password(void)
|
|||
{
|
||||
size_t len;
|
||||
char *password = NULL;
|
||||
|
||||
printf("Enter password to encrypt: \n");
|
||||
|
||||
getline(&password, &len, stdin);
|
||||
/* remove newline character */
|
||||
password[strcspn(password, "\n")] = '\0';
|
||||
|
@ -153,98 +156,72 @@ char *get_password(void)
|
|||
void encrypt_password(const char *name, char *password)
|
||||
{
|
||||
char *m_key = get_master_key();
|
||||
unsigned char salt[SALT_SIZE];
|
||||
unsigned char key[KEY_SIZE];
|
||||
unsigned char nonce[NONCE_SIZE];
|
||||
uint8_t key[KEY_SIZE];
|
||||
uint8_t iv[IV_SIZE];
|
||||
|
||||
/* generate random bytes for salt and nonce(number used once) */
|
||||
randombytes_buf(salt, sizeof(salt));
|
||||
randombytes_buf(nonce, sizeof(nonce));
|
||||
/* generate random bytes for iv */
|
||||
random_bytes(iv, sizeof(iv));
|
||||
/* hash master password to give us the key for encrypting the password */
|
||||
if (crypto_pwhash(key, sizeof(key), m_key, strlen(m_key), salt,
|
||||
crypto_pwhash_OPSLIMIT_INTERACTIVE,
|
||||
crypto_pwhash_MEMLIMIT_INTERACTIVE,
|
||||
crypto_pwhash_ALG_DEFAULT) != 0) {
|
||||
sodium_free(m_key);
|
||||
perror("apm");
|
||||
die("Cannot create key");
|
||||
}
|
||||
size_t pw_len = strlen(password);
|
||||
/* Include space for authentication tag */
|
||||
size_t ciphered_len = crypto_secretbox_MACBYTES + pw_len;
|
||||
char ciphered[ciphered_len];
|
||||
blake2b(key, KEY_SIZE, NULL, 0, m_key, strlen(m_key));
|
||||
|
||||
/* encrypt password */
|
||||
if (crypto_secretbox_easy((unsigned char *) ciphered,
|
||||
(unsigned char *) password, pw_len,
|
||||
nonce, key) != 0) {
|
||||
sodium_free(m_key);
|
||||
die("Error encrypting password");
|
||||
}
|
||||
size_t pw_len = strlen(password);
|
||||
/* find last \n and replace with 0 */
|
||||
strrchr(password, '\n')[0] = '\0';
|
||||
char data[1024]; /* max 1024 bytes */
|
||||
strcpy(data, password);
|
||||
|
||||
size_t data_len = EncryptData((uint8_t *) data, pw_len, key, iv);
|
||||
|
||||
char *filepath = get_passfile(name);
|
||||
FILE *file = fopen(filepath, "wb");
|
||||
if (file == NULL) {
|
||||
sodium_free(m_key);
|
||||
free(m_key);
|
||||
free(filepath);
|
||||
die("Error opening pass file to write");
|
||||
}
|
||||
|
||||
fwrite(salt, sizeof(salt), 1, file);
|
||||
fwrite(nonce, sizeof(nonce), 1, file);
|
||||
fwrite(ciphered, ciphered_len, 1, file);
|
||||
fwrite(iv, sizeof(iv), 1, file);
|
||||
fwrite(data, data_len, 1, file);
|
||||
|
||||
fclose(file);
|
||||
free(filepath);
|
||||
sodium_free(m_key);
|
||||
free(m_key);
|
||||
}
|
||||
|
||||
void decrypt_password(const char *name, int open)
|
||||
{
|
||||
char *m_key = get_master_key();
|
||||
unsigned char salt[SALT_SIZE];
|
||||
unsigned char nonce[NONCE_SIZE];
|
||||
unsigned char key[KEY_SIZE];
|
||||
uint8_t key[KEY_SIZE];
|
||||
uint8_t iv[IV_SIZE];
|
||||
|
||||
char *filepath = get_passfile(name);
|
||||
FILE *file = fopen(filepath, "rb");
|
||||
if (file == NULL) {
|
||||
sodium_free(m_key);
|
||||
free(m_key);
|
||||
free(filepath);
|
||||
die("Error opening pass file to read");
|
||||
}
|
||||
|
||||
/* get salt and nonce from file */
|
||||
fread(salt, sizeof(char), sizeof(salt), file);
|
||||
fread(nonce, sizeof(char), sizeof(nonce), file);
|
||||
/* get iv from file */
|
||||
fread(iv, 1, sizeof(iv), file);
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
size_t ciphered_len = ftell(file) - sizeof(salt) - sizeof(nonce);
|
||||
fseek(file, sizeof(salt) + sizeof(nonce), SEEK_SET);
|
||||
|
||||
char ciphered[ciphered_len];
|
||||
fread(ciphered, sizeof(char), ciphered_len, file);
|
||||
if (crypto_pwhash(key, sizeof(key), m_key, strlen(m_key), salt,
|
||||
crypto_pwhash_OPSLIMIT_INTERACTIVE,
|
||||
crypto_pwhash_MEMLIMIT_INTERACTIVE,
|
||||
crypto_pwhash_ALG_DEFAULT) != 0) {
|
||||
sodium_free(m_key);
|
||||
free(filepath);
|
||||
perror("apm");
|
||||
die("Cannot create key");
|
||||
}
|
||||
/* take authentication bytes away */
|
||||
size_t deciphered_len = ciphered_len - crypto_secretbox_MACBYTES;
|
||||
char deciphered[deciphered_len];
|
||||
if (crypto_secretbox_open_easy((unsigned char *) deciphered,
|
||||
(unsigned char *) ciphered, ciphered_len, nonce, key) != 0) {
|
||||
sodium_free(m_key);
|
||||
if (ftell(file) <= sizeof(iv)) {
|
||||
free(m_key);
|
||||
free(filepath);
|
||||
fclose(file);
|
||||
die("Error decrypting password");
|
||||
die("Empty file");
|
||||
}
|
||||
size_t ciphered_len = ftell(file) - sizeof(iv);
|
||||
fseek(file, sizeof(iv), SEEK_SET);
|
||||
|
||||
char ciphered[ciphered_len];
|
||||
fread(ciphered, 1, ciphered_len, file);
|
||||
blake2b(key, KEY_SIZE, NULL, 0, m_key, strlen(m_key));
|
||||
|
||||
size_t data_len = DecryptData((uint8_t *) ciphered, ciphered_len, key, iv);
|
||||
ciphered[data_len] = '\0';
|
||||
|
||||
deciphered[deciphered_len] = '\0';
|
||||
if (open) {
|
||||
char *editor = getenv("EDITOR");
|
||||
if (editor == NULL) {
|
||||
|
@ -252,7 +229,7 @@ void decrypt_password(const char *name, int open)
|
|||
}
|
||||
char tmp_f[] = "/tmp/apm";
|
||||
FILE *tmp = fopen(tmp_f, "w+");
|
||||
fprintf(tmp, "%s\n", deciphered);
|
||||
fprintf(tmp, "%s\n", ciphered);
|
||||
fclose(tmp);
|
||||
char *cmd = memalloc(strlen(editor) + strlen(tmp_f) + 1);
|
||||
sprintf(cmd, "%s %s", editor, tmp_f);
|
||||
|
@ -267,10 +244,10 @@ void decrypt_password(const char *name, int open)
|
|||
encrypt_password(name, content);
|
||||
fclose(tmp);
|
||||
} else {
|
||||
printf("%s\n", deciphered);
|
||||
printf("%s\n", ciphered);
|
||||
}
|
||||
|
||||
sodium_free(m_key);
|
||||
free(m_key);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
|
@ -287,11 +264,7 @@ char *get_master_key(void)
|
|||
struct stat st;
|
||||
if ((fstat(fileno(key_file), &st) == 0) || (S_ISREG(st.st_mode))) {
|
||||
size_t pass_size = st.st_size;
|
||||
m_key = (char *) sodium_malloc(pass_size);
|
||||
if (m_key == NULL) {
|
||||
perror("apm");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
m_key = memalloc(pass_size);
|
||||
if (fgets(m_key, pass_size, key_file) == NULL) {
|
||||
perror("apm");
|
||||
exit(EXIT_FAILURE);
|
||||
|
@ -304,6 +277,16 @@ char *get_master_key(void)
|
|||
return m_key;
|
||||
}
|
||||
|
||||
void random_bytes(uint8_t *bytes, size_t size)
|
||||
{
|
||||
FILE *file = fopen("/dev/urandom", "r");
|
||||
if (file == NULL) {
|
||||
die("Cannot open /dev/urandom");
|
||||
}
|
||||
fread(bytes, 1, size, file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
void generate_password(char *name, int length)
|
||||
{
|
||||
srand(time(NULL));
|
||||
|
@ -325,12 +308,8 @@ void die(char *str)
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (sodium_init() == -1) {
|
||||
die("Error initializing sodium");
|
||||
}
|
||||
|
||||
/* disable core dump for security */
|
||||
setrlimit(RLIMIT_CORE, &(struct rlimit) {0, 0});
|
||||
|
||||
|
|
191
blake2b.h
Normal file
191
blake2b.h
Normal file
|
@ -0,0 +1,191 @@
|
|||
// https://github.com/jamesvan2019/blake2b_c
|
||||
// BLAKE2b Hashing Context and API Prototypes
|
||||
|
||||
#ifndef BLAKE2B_H
|
||||
#define BLAKE2B_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// state context
|
||||
typedef struct {
|
||||
uint8_t b[128]; // input buffer
|
||||
uint64_t h[8]; // chained state
|
||||
uint64_t t[2]; // total number of bytes
|
||||
size_t c; // pointer for b[]
|
||||
size_t outlen; // digest size
|
||||
} blake2b_ctx;
|
||||
|
||||
// Cyclic right rotation.
|
||||
|
||||
#ifndef ROTR64
|
||||
#define ROTR64(x, y) (((x) >> (y)) ^ ((x) << (64 - (y))))
|
||||
#endif
|
||||
|
||||
// Little-endian byte access.
|
||||
|
||||
#define B2B_GET64(p) \
|
||||
(((uint64_t) ((uint8_t *) (p))[0]) ^ \
|
||||
(((uint64_t) ((uint8_t *) (p))[1]) << 8) ^ \
|
||||
(((uint64_t) ((uint8_t *) (p))[2]) << 16) ^ \
|
||||
(((uint64_t) ((uint8_t *) (p))[3]) << 24) ^ \
|
||||
(((uint64_t) ((uint8_t *) (p))[4]) << 32) ^ \
|
||||
(((uint64_t) ((uint8_t *) (p))[5]) << 40) ^ \
|
||||
(((uint64_t) ((uint8_t *) (p))[6]) << 48) ^ \
|
||||
(((uint64_t) ((uint8_t *) (p))[7]) << 56))
|
||||
|
||||
// G Mixing function.
|
||||
|
||||
#define B2B_G(a, b, c, d, x, y) { \
|
||||
v[a] = v[a] + v[b] + x; \
|
||||
v[d] = ROTR64(v[d] ^ v[a], 32); \
|
||||
v[c] = v[c] + v[d]; \
|
||||
v[b] = ROTR64(v[b] ^ v[c], 24); \
|
||||
v[a] = v[a] + v[b] + y; \
|
||||
v[d] = ROTR64(v[d] ^ v[a], 16); \
|
||||
v[c] = v[c] + v[d]; \
|
||||
v[b] = ROTR64(v[b] ^ v[c], 63); }
|
||||
|
||||
// Initialization Vector.
|
||||
|
||||
static const uint64_t blake2b_iv[8] = {
|
||||
0x6A09E667F3BCC908, 0xBB67AE8584CAA73B,
|
||||
0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1,
|
||||
0x510E527FADE682D1, 0x9B05688C2B3E6C1F,
|
||||
0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179
|
||||
};
|
||||
|
||||
// Compression function. "last" flag indicates last block.
|
||||
|
||||
static void blake2b_compress(blake2b_ctx *ctx, int last)
|
||||
{
|
||||
const uint8_t sigma[12][16] = {
|
||||
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
|
||||
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
|
||||
{ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
|
||||
{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
|
||||
{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
|
||||
{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
|
||||
{ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
|
||||
{ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
|
||||
{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
|
||||
{ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
|
||||
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
|
||||
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }
|
||||
};
|
||||
int i;
|
||||
uint64_t v[16], m[16];
|
||||
|
||||
for (i = 0; i < 8; i++) { // init work variables
|
||||
v[i] = ctx->h[i];
|
||||
v[i + 8] = blake2b_iv[i];
|
||||
}
|
||||
|
||||
v[12] ^= ctx->t[0]; // low 64 bits of offset
|
||||
v[13] ^= ctx->t[1]; // high 64 bits
|
||||
if (last) // last block flag set ?
|
||||
v[14] = ~v[14];
|
||||
|
||||
for (i = 0; i < 16; i++) // get little-endian words
|
||||
m[i] = B2B_GET64(&ctx->b[8 * i]);
|
||||
|
||||
for (i = 0; i < 12; i++) { // twelve rounds
|
||||
B2B_G( 0, 4, 8, 12, m[sigma[i][ 0]], m[sigma[i][ 1]]);
|
||||
B2B_G( 1, 5, 9, 13, m[sigma[i][ 2]], m[sigma[i][ 3]]);
|
||||
B2B_G( 2, 6, 10, 14, m[sigma[i][ 4]], m[sigma[i][ 5]]);
|
||||
B2B_G( 3, 7, 11, 15, m[sigma[i][ 6]], m[sigma[i][ 7]]);
|
||||
B2B_G( 0, 5, 10, 15, m[sigma[i][ 8]], m[sigma[i][ 9]]);
|
||||
B2B_G( 1, 6, 11, 12, m[sigma[i][10]], m[sigma[i][11]]);
|
||||
B2B_G( 2, 7, 8, 13, m[sigma[i][12]], m[sigma[i][13]]);
|
||||
B2B_G( 3, 4, 9, 14, m[sigma[i][14]], m[sigma[i][15]]);
|
||||
}
|
||||
|
||||
for( i = 0; i < 8; ++i )
|
||||
ctx->h[i] ^= v[i] ^ v[i + 8];
|
||||
}
|
||||
|
||||
// Add "inlen" bytes from "in" into the hash.
|
||||
void blake2b_update(blake2b_ctx *ctx, // context
|
||||
const void *in, size_t inlen) // data to be hashed
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < inlen; i++) {
|
||||
if (ctx->c == 128) { // buffer full ?
|
||||
ctx->t[0] += ctx->c; // add counters
|
||||
if (ctx->t[0] < ctx->c) // carry overflow ?
|
||||
ctx->t[1]++; // high word
|
||||
blake2b_compress(ctx, 0); // compress (not last)
|
||||
ctx->c = 0; // counter to zero
|
||||
}
|
||||
ctx->b[ctx->c++] = ((const uint8_t *) in)[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the hashing context "ctx" with optional key "key".
|
||||
// 1 <= outlen <= 64 gives the digest size in bytes.
|
||||
// Secret key (also <= 64 bytes) is optional (keylen = 0).
|
||||
int blake2b_init(blake2b_ctx *ctx, size_t outlen,
|
||||
const void *key, size_t keylen) // secret key
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (outlen == 0 || outlen > 64 || keylen > 64)
|
||||
return -1; // illegal parameters
|
||||
|
||||
for (i = 0; i < 8; i++) // state, "param block"
|
||||
ctx->h[i] = blake2b_iv[i];
|
||||
ctx->h[0] ^= 0x01010000 ^ (keylen << 8) ^ outlen;
|
||||
|
||||
ctx->t[0] = 0; // input count low word
|
||||
ctx->t[1] = 0; // input count high word
|
||||
ctx->c = 0; // pointer within buffer
|
||||
ctx->outlen = outlen;
|
||||
|
||||
for (i = keylen; i < 128; i++) // zero input block
|
||||
ctx->b[i] = 0;
|
||||
if (keylen > 0) {
|
||||
blake2b_update(ctx, key, keylen);
|
||||
ctx->c = 128; // at the end
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Generate the message digest (size given in init).
|
||||
// Result placed in "out".
|
||||
void blake2b_final(blake2b_ctx *ctx, void *out)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
ctx->t[0] += ctx->c; // mark last block offset
|
||||
if (ctx->t[0] < ctx->c) // carry overflow
|
||||
ctx->t[1]++; // high word
|
||||
|
||||
while (ctx->c < 128) // fill up with zeros
|
||||
ctx->b[ctx->c++] = 0;
|
||||
blake2b_compress(ctx, 1); // final block flag = 1
|
||||
|
||||
// little endian convert and store
|
||||
for (i = 0; i < ctx->outlen; i++) {
|
||||
((uint8_t *) out)[i] =
|
||||
(ctx->h[i >> 3] >> (8 * (i & 7))) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
// All-in-one convenience function.
|
||||
int blake2b(void *out, size_t outlen, // return buffer for digest
|
||||
const void *key, size_t keylen, // optional secret key
|
||||
const void *in, size_t inlen) // data to be hashed
|
||||
{
|
||||
blake2b_ctx ctx;
|
||||
|
||||
if (blake2b_init(&ctx, outlen, key, keylen))
|
||||
return -1;
|
||||
blake2b_update(&ctx, in, inlen);
|
||||
blake2b_final(&ctx, out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue