Given a private key and a X.509 certificate (no RSA), I want to confirm that:
The private key is "valid".
It matches the certificate.
This validation would run in nodejs.
Example of valid private key (don't worry, testing purposes):
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgSVkfyOqQ4E6No+v6
h/wotfYuGqiqierJ2YXx2v3rP1GhRANCAASMlEMAwv9jf8FAKDAxrnPGWVGBBzbD
wt3VQrrM5i/DOwCzF1XH7v6iYbvpYe9P0Qvf5ndqYYBklqLkXHAR37Vz
-----END PRIVATE KEY-----
Example of matching certificate:
-----BEGIN CERTIFICATE-----
MIICkDCCAjegAwIBAgIUCL+kBzVdqMGzurpuYwIxkuLbYrgwCgYIKoZIzj0EAwIw
czELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh
biBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT
E2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTkwMjA1MDgyMjAwWhcNMjAwMjA1MDgy
NzAwWjBDMTAwDQYDVQQLEwZjbGllbnQwCwYDVQQLEwRvcmcxMBIGA1UECxMLZGVw
YXJ0bWVudDExDzANBgNVBAMTBm5hdGhhbjBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABIyUQwDC/2N/wUAoMDGuc8ZZUYEHNsPC3dVCuszmL8M7ALMXVcfu/qJhu+lh
70/RC9/md2phgGSWouRccBHftXOjgdgwgdUwDgYDVR0PAQH/BAQDAgeAMAwGA1Ud
EwEB/wQCMAAwHQYDVR0OBBYEFJYSgUGno6j2eYUKjLs9BRzreUY1MCsGA1UdIwQk
MCKAIEI5qg3NdtruuLoM2nAYUdFFBNMarRst3dusalc2Xkl8MGkGCCoDBAUGBwgB
BF17ImF0dHJzIjp7ImhmLkFmZmlsaWF0aW9uIjoib3JnMS5kZXBhcnRtZW50MSIs
ImhmLkVucm9sbG1lbnRJRCI6Im5hdGhhbiIsImhmLlR5cGUiOiJjbGllbnQifX0w
CgYIKoZIzj0EAwIDRwAwRAIgbYQ4UscWT5rgqLwrhcj8kRNN0kfA5n12Zpl1Fclw
+7QCIAlTx9oMsGBAeaNxJ3PV6mo9Zng5aMNnAmwW2PVcDlXt
-----END CERTIFICATE-----
You could attempt to create a secure context, using the built in node api. It will throw if the certificates don't match:
import tls from 'tls';
function testCertAndKeyMatch (cert, key) {
try {
tls.createSecureContext({ cert, key });
return true;
} catch (error) {
if (error.code === 'ERR_OSSL_X509_KEY_VALUES_MISMATCH') {
return false;
}
throw error;
}
}
You can use it like:
const cert = fs.readFileSync('./certs/cert.pem', 'utf8');
const key = fs.readFileSync('./certs/key.pem', 'utf8');
const isMatch = testCertAndKeyMatch(cert, key);
Related
I am generating a RSA key pair in a django view, storing the keys in db, sending the public key to the client template, encrypting a message, then sending the message to a different view where I am retrieving the private key from the db and trying to decrypt the message. When I do the decryption, I get ValueError: Incorrect decryption..
Server: Generate keys, store in db, send public key to client:
def generateKeys(request):
store = Keys.objects.filter(pk=request.GET.get('pk'))
serverkeypair = Crypto.PublicKey.RSA.generate(4096)
spk = serverkeypair.publickey().exportKey(format='PEM', passphrase=None, pkcs=8, protection=None, randfunc=None)
sprk = serverkeypair.exportKey(format='PEM', passphrase=None, pkcs=8, protection=None, randfunc=None)
spk = spk.decode()
store.update(spk = spk)
store.update(sprk = sprk.decode())
return JsonResponse(['success', spk], safe=False)
Client: Import public key, encrypt message:
fetch('../api/endpoint1/?pk='+pk, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
}).then(response => response.json())
.then(async data => {
var pbk = data[1];
pbk = pbk.substring(27, pbk.length - 25);
var plaintext = window.crypto.getRandomValues(new Uint8Array(64));
console.log(btoa(ab2str(plaintext)));
window.crypto.subtle.importKey(
"spki",
str2ab(atob(pbk)),
{
name: "RSA-OAEP",
hash: "SHA-256"
},
true,
["encrypt"]
).then((imp_key) => {
window.crypto.subtle.encrypt({name: 'RSA-OAEP'}, imp_key, plaintext).then((ciphertext) => {
ciphertext = btoa(ab2str(ciphertext));
console.log(ciphertext);
ciphertext = b64_to_b64url(ciphertext);
fetch('/api/endpoint2/?pk='+pk+'&ciph='+ciphertext);
});
});
});
/*
Convert an ArrayBuffer into a string
from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
*/
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
/*
Convert a string into an ArrayBuffer
from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
*/
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
function b64_to_b64url(s) {
return s.replace(/\+/g, '-').replace(/\//g, '_');
}
Server: Retrieve key, decrypt message:
def decrypt(request):
store = Keys.objects.get(pk=request.GET.get('pk'))
sprk = getattr(store, 'sprk')
key = RSA.importKey(sprk.encode())
cipher = PKCS1_OAEP.new(key)
ciphertext = request.GET.get('ciph')
ciphertext = base64.b64decode(b64url_to_b64(ciphertext))
plaintext = cipher.decrypt(ciphertext)
print(plaintext)
return JsonResponse(['success'], safe=False)
def b64url_to_b64(str):
return str.replace('-', '+').replace('_', '/')
Test data:
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoxvH93Qpcbg75fMMkBxp
gWNaIamVo4dvwc7xi8o2JQTYOU+wzCYE5Hs7qN2YoPMJdDQHNqvgIfHpfpLwcIaA
F2qrojRLA8YvJoKpamlg/ui0xyu8UNtYgkfhatwihHOGl/eYkzqgeu1JQ6maF/PO
MfVv6ulioTDPEZaREx4Gj6iVgLRhW7eRu0oYG4tGoTmzmze1sIbxZvTkZWDdp9Y2
S6dsUHllsROUFUH2d/RxKJXoG7jEMjki8l+CaxkqNxjffQGBkg060Pj+Os6QbICZ
Ql4KYp+KNsRhQduaJkYsYuA5k/bl+BIWXh9E5o2eFNPQ0d6qm72Zl+ryxBwno2Sj
JYlYN9foRYxJB+2E759eAnLASK3amZaeQRrzMdbsWLzRDJDTSBzvqqmK2b5tFe7W
GM7JyVg/sHwLlY+hVoIQ8+WbuV/knxm3IV6XXUhg8VtFtlFYdcY4yVwO1K7yFzQN
h7mZjC8AhOGsw7oqzzExEasMdIsJ8DdkdtRUYJjcQaQiPU9luyVNvMFIZ4wkUTvm
FHyB5kHhwT7uWkFfkj9HXle9CQxhdlQIM2kfc3H7XBH+l4qf90pjg0cP7iG1semc
jyvv0WcFmatsAEBQ+XbIxqw76XtWh8UNGgypkI0PlVVRjKauywH6YofsjTATsE7H
asL5I39I2/XxJ9gg+0rqD3kCAwEAAQ==
-----END PUBLIC KEY-----
-----BEGIN PRIVATE KEY-----
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\nh78PtsliPE9KF/6EGS6Ysagxa2HuOQ==
-----END PRIVATE KEY-----
Plaintext (converted to string, then base64 encoded):
2w+c4ONLd50j1uipIXJ4yJlMnD1UtYuYRK6fTchr2RM4XMTz6vpB21Qu4qFIjJJGavub/OhV29c/8PB82L4www==
Ciphertext:
fIi24T8TXlYWyl+SfTVIkmF/aBc1nYPbsdtaMGQwtrOxDWjwEW7rK4b5i1dgGq3dpH2nxTxx0HwunFsD51I5I+XiGjeODGubSbM4+YNplFnu+cQ6YcN9M4jNVKkYrkDvEQUtyrM/G3oYIjZSVSh30Yb2ZyR5Tw5Tx+PAzXadUi/aUpyk0Yo92saBFGPRZMFxKuE4mozQJuNYug/qmk8+VTUd4djsWx+MFgEb+99/dTvupHu25mXA/NOFqw6t3SF4J3PW8nwlrztMnyrMKaLVJAWjNRYRDbroM3PkRG2dPbNVtg0d5e+GeVQvGFoaV2xukkP+QS2EqlQcWE1UdMvSHOx9jKp01EFrYoBjPgu/LlU1gpcqoA8yHIjiNaZ1mEpck0znKPeZmxPMh3n3XBGTO4U6t1/aPgBmn5FD8r7tPw2HJQmDEDpoGRsD+9T9CjrgGoB8Cpt2VQBqA1BPBfhrIKUdY/HHj5zCF5RHYujdMNn/qmLleN2fH/Ee3kFZ83s27fGpkwJIZD6Z9NMvIGfwJ9JKLM8xnm26KcXBKgml0Qw6py/9tE0WasEhWlv4KQOdfVfdjP6yp+wzNH8SgjBAMy6ANhCLLiY6MzgenDKPJBPZK/z+QiVakSOJ/boSpckjxDxnxo4LA+yZkWYwsR9UjWH9IC/Uj3ud23yiDawqfdk=
Encryption and decryption apply RSA with OAEP as padding. However, in the WebCrypto code SHA-256 is explicitly specified for content and MGF1 digests, while in the PyCryptodome code the default value SHA-1 is used for both digests when instantiating the cipher object, see PKCS1_OAEP and also this post of yours.
To fix the issue, pass SHA256 as second parameter in the PyCryptodome code when instantiating the cipher object:
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256
...
cipher = PKCS1_OAEP.new(key, SHA256)
then decryption is successful.
I'm able to decrypt a JWE with an older version of jose but I'm struggling to use the latest version API.
My token headers are the following:
{
"alg": "A128KW",
"enc": "A128CBC-HS256",
"typ": "JWT"
}
With jose 2.0.3 this code (copied from another stackoverflow post) is working to decrypt the payload:
const { JWK, JWE } = require('jose');
const privateKey = JWK.asKey("my key");
const jwe = "my JWE"
const jwt = JWE.decrypt(jwe, privateKey);
const payload = Buffer.from(jwt.toString().split('.')[1], 'base64');
const data = JSON.parse(payload);
Obviously I cannot share the the private key. It's a string with 16 characters.
I tried to used a later version of jose (4.9.2) but the API changed totally. I tried this but it does not end well:
const jose = require('jose');
const jwe = "my token";
const key = await jose.importJWK({ kty: 'oct', k: 'my key', alg: 'A128CBC-HS256' })
const { plaintext, protectedHeader } = await jose.compactDecrypt(jwe, key);
console.log(protectedHeader)
console.log(new TextDecoder().decode(plaintext))
I get this error :
TypeError: Invalid key size for alg: A128KW
I confirm my key is 16 characters long (so 128 bits normally) so I don't get this error. Any hint?
The issue is your secret key. You're using 16 character string and passing it in as a JWK. JWK k is meant to be base64url encoded, so your 16 characters "encoded" becomes a 12 byte secret when "decoded".
Here's what will work, don't bother with a key import for symmetric secrets, just pass a Buffer instance instead.
const { plaintext, protectedHeader } = await jose.compactDecrypt(jwe, Buffer.from('my 16 bytes secret'));
console.log(protectedHeader)
console.log(new TextDecoder().decode(plaintext))
Additionally, if your payload is a JWT claims set, you might as well do a proper JWT validation combined with the decrypt operation. docs
const { plaintext, protectedHeader } = await jose.jwtDecrypt(jwe, Buffer.from('my 16 bytes secret'));
console.log(protectedHeader);
console.log(payload);
This is full example about jwt and jwe.
Full project -- Generation -- Validation -- How to use
Generation jwt and jwe
let jwt = require('jsonwebtoken'),
{
JWK,
parse
} = require('node-jose');
async function getJwtEncrypt(raw, format = 'compact', contentAlg = 'A256GCM', alg = 'RSA-OAEP') {
let publicKey = await JWK.asKey(process.env.JWT_PUBLIC_KEY, 'pem');
const buffer = Buffer.from(JSON.stringify(raw));
return await JWE.createEncrypt(
{format: format, contentAlg: contentAlg, fields: {alg: alg}}, publicKey)
.update(buffer).final();
}
function getJwtSign(payload, subject) {
const SIGN_OPTIONS = {
subject: `${subject}`,
expiresIn: payload.expiresIn,
algorithm: 'RS256'
};
return jwt.sign(payload, process.env.PRAIVATE_KEY, SIGN_OPTIONS);
}
Validation jwt and jwe
let jwt ,
{
TokenExpiredError,
JsonWebTokenError
} = require('jsonwebtoken'),
{
JWK,
parse
} = require('node-jose');
function getJwtVerify(token, cb) {
try {
jwt.verify(token, process.env.PUBLIC_KEY, {}, (err, decoded) => {
if (err !== null && err instanceof TokenExpiredError) {
cb('TOKEN_EXP');
return Json.builder(Response.HTTP_UNAUTHORIZED_TOKEN_EXP);
}
if (err instanceof JsonWebTokenError) {
cb('IN_VALID_TOKEN');
return Json.builder(Response.HTTP_UNAUTHORIZED_INVALID_TOKEN);
}
cb(decoded);
});
} catch (e) {
ValidationException(e);
}
}
async function getJwtDecrypt(encryptedBody) {
try {
let keystore = JWK.createKeyStore();
await keystore.add(await JWK.asKey(process.env.JWE_PRAIVATE_KEY, 'pem'));
let outPut = parse.compact(encryptedBody);
let decryptedVal = await outPut.perform(keystore);
let token = Buffer.from(decryptedVal.plaintext).toString();
if (typeof decryptedVal.plaintext === ('undefined' || null))
return Json.builder(Response.HTTP_UNAUTHORIZED_INVALID_TOKEN);
return token.replace(/["]+/g, '');
} catch (e) {
ValidationException(e);
}
}
.env file
PRAIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDHRyVyzQTw5XkTaM3bgfKE5Ry+CqqmijpvHprM1Dvkr3q7lR2A
ZTzKGnyHVbnVpKPga8AHs3E7l/MVci2BhWLbHGOn4GgrrqDmItvCvFcfGZM9USZE
2tp5zLj3aBoqImk+niFHQipSPus9JhNU9HkLNTUJGwuuxfWuXwtnMhWcpwIDAQAB
AoGAMs9bJwhLSDjaRC6mvl9FvMjGKVaC6G+6Mnb1NWCv3ME5Y/bDTOeDNmzGb6NV
/Lk7547RqaUBLBa0LjWskKe36n6hfWExEaRe9ikqRiz6y0QXyfO5h6qZD0YXSsIu
5VY8LBCz45Z330I296jLbgkZ2OhZbEj8lk8rf99JtzGzRBECQQD1W0cBRIKt6e8g
KJNLp8A0dLId6bbmgG5xnOscTMfxzZAvMAz6eN4ur8vkJKDVr1YjRCHETrfdaX/d
kfY/29TtAkEAz+wokyzPERFA+XCdLLW9d9b3nPQhTUodBIq9EXfnhPAXRWzjObLl
zItS3bDzWpGqfwP7nc9zjpcqY0zKdJ+5YwJASnyDeecKpTG33tNypC0xNLuYt2wU
krW60dMJrXXB3a7CbxDvX7sB+Lp187UK/tRUGjC875PWTemRX/rH/2sFoQJBAIN8
MFyB5aBBbPlRAdQYSezTAFs89yJNT/RjWBUH4lzrB4xbw4XlX/Tt1kVjdUE9BLi1
6BRv7/+oEKIjGZSOvUkCQEdb/CPaqXPfn7zA/SKL3hgJbbEo8aowOM4VfV+ZVojp
vzvj/ZjQpRKDEJ7iiJCXBUT59BwPuirq8v6fjWMRneU=
-----END RSA PRIVATE KEY-----"
PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHRyVyzQTw5XkTaM3bgfKE5Ry+
CqqmijpvHprM1Dvkr3q7lR2AZTzKGnyHVbnVpKPga8AHs3E7l/MVci2BhWLbHGOn
4GgrrqDmItvCvFcfGZM9USZE2tp5zLj3aBoqImk+niFHQipSPus9JhNU9HkLNTUJ
GwuuxfWuXwtnMhWcpwIDAQAB
-----END PUBLIC KEY-----"
JWE_PRAIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----
MIICWgIBAAKBgFsiRgcjHE4S0uoNvOEz/YdmPRRBo+APWpWpM8+B2JSXNZdQCoin
7psz8Pup+4C95Qz/R5Zhw/IZBfvOpD6Hj9v9cMgnnMjPYW6eecByJ6S6lynetRLT
Igy4ICRynhx4rHvRwXpr6dEeaazArDJlwutMv9cW7DdSPiqRSTl9YAffAgMBAAEC
gYAsZ1qvh4/3CnzxxZfOMsLJAiuofwMV3OVKHpM7/AxG+hYGj91SEGDWBkzYkk4U
wHGmD4wV3bTXdRHRSzIDtZGGDZbneWN58TRUPuSP1XiNVIb8Doaj91cprRiBxlmM
ZHJsix/OLN/Sm3UjuZolSjG3K0QucrrBUtPBfhxJ5GXmYQJBAKOwYZHyhz9gGrW7
zkkF9Fivu9P/H0/eS4HaTQLYEUG475KIj6xQLrjTRtxZ139DV3jrGoLEapFvAaOk
fETiBwMCQQCOhy7Aa5muKcELFJqxzY1CWLCaayX43ZPVYKCkLaqDYRWtufLjZ3vj
hWiQScULEGLiM+axZFgTmbtWI6oy4cb1AkBT+UeEzQvvSklJlChWs/RPjw/nyQjy
O1M3MZuyatAnjE1zOhWiy5u8e77tijWQdyanxMzb6xHUvEL2BYsu91mrAkAbq/tT
uJBZ1Bl6wUFXjAUFAJspH+x7aOmu39fQiF02rL68wAF8TTcscVZfzTLIdyH7sP/1
KPpAs/Q/QSVmQ5eRAkBHWQgmTC5MsmLmJO6UdUjOp5a9rJzxnMej8zUiR/lZ7ssc
4jAfqi8ukBv+5THumh3TKDNPAu9+r5nWOahb/jrJ
-----END RSA PRIVATE KEY-----"
JWT_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgFsiRgcjHE4S0uoNvOEz/YdmPRRB
o+APWpWpM8+B2JSXNZdQCoin7psz8Pup+4C95Qz/R5Zhw/IZBfvOpD6Hj9v9cMgn
nMjPYW6eecByJ6S6lynetRLTIgy4ICRynhx4rHvRwXpr6dEeaazArDJlwutMv9cW
7DdSPiqRSTl9YAffAgMBAAE=
-----END PUBLIC KEY-----"
I am a new in Nodejs and libsodium.js. I have a task but I don't know how to solve it, and from where to start it. I'll appreciate if someone guides me. I have a class wallet inside it I need to generate public and private key then
class Wallet {
async create(): Promise<void> {
// must generate public / private key pair
// you can store files in the leveldb
}
async sign(content: string): Promise<string> {
// must return signature for the given content using previously generated public / private key pair
}
async verify(signature: string, content: string): Promise<boolean> {
// verifies if given signature was generated by previously generated public / private key pair
}
async encrypt(content: string): Promise<string> {
// must encrypt given string and return encrypted string (based on previously generated public / private key pair)
}
async decrypt(content): Promise<string> {
// must decrypt the given encrypted string and return original string (based on previously generated public / private key pair)
}
}
// usage
const wallet = new Wallet()
await wallet.create()
await wallet.sign("I am president") // must return signature
await wallet.verify(signature, "I am president") // must return true
await wallet.encrypt("I am president") // must return ecnrypted string
await wallet.decrypt(encrypted) // must return decrypted string
Not sure if I am doing something wrong but using this api https://www.firebase.com/docs/security/simple-login-email-password.html I can successfully create a user - according to the return message, but I can not see that user anywhere in the Forge console. How do you know what users are registered?
Should I be taking the return user ID and creating my own user object in Firebase or is this duplication unnecessary. I do need to add some additional user properties so perhapes I will need to do this anyway.
When using email / password authentication in Firebase Authentication (previously known as Firebase SimpleLogin), your user's email and password combination is securely stored separately from the data actually stored in your Firebase.
This barrier between the data in your Firebase and your users' email / password hash combinations is by design: we want to make it easier for you to (1) develop your application, (2) prevent any accidental user credential leaks, and (3) still give you total flexibility with how to store your user data in Firebase.
That means that we only store the email address / password hash combination and nothing else, so it is up to you to decide how to store actual user data in your Firebase. As you suggested, you should be taking the user id and storing that data in your Firebase in a location such as /users/$id, and using the Firebase Security Rules Language to determine read / write access to that data. Your user's unique id and email are already in the auth variable you'll use when writing rules.
Here i created an Android program to do what Rob said for firebase beginner(like me)
out there.this program first store the username of the signedUp or signedIn user and then display them in a listView
SignInActivity.java
public class SignInActivity extends BaseActivity implements View.OnClickListener,View.OnKeyListener{
private DatabaseReference mDatabase;
public static FirebaseAuth mAuth;
private static final String TAG = "MainActivity";
EditText usernameField;
EditText passwordField;
TextView changeSignUpModeTextView;
Button signUpButton;
ImageView logo;
RelativeLayout relativeLayout;
Boolean signUpModeActive;
static ArrayList<String> userList = new ArrayList<>();
#Override
public void onStart() {
super.onStart();
// Check auth on Activity start
if (mAuth.getCurrentUser() != null) {
onAuthSuccess(mAuth.getCurrentUser());
}
}
#Override
public boolean onKey(View view, int i, KeyEvent keyEvent) {
if(i == keyEvent.KEYCODE_ENTER && keyEvent.getAction() == keyEvent.ACTION_DOWN){
signUpOrLogIn(view);
}
return false;
}
#Override
public void onClick(View view) {
if(view.getId() == R.id.changeSignUpMode){
if (signUpModeActive == true){
signUpModeActive = false;
changeSignUpModeTextView.setText("Sign Up");
signUpButton.setText("Log In");
}else{
signUpModeActive = true;
changeSignUpModeTextView.setText("Log In");
signUpButton.setText("Sign Up");
}
}else if(view.getId() == R.id.logo || view.getId() == R.id.relativeLayout){
InputMethodManager inm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
inm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),0);
}
}
public void signUpOrLogIn(View view) {
showProgressDialog();
String email = usernameField.getText().toString().trim();
String password = passwordField.getText().toString().trim();
if (signUpModeActive == true) {
mAuth.createUserWithEmailAndPassword(email,password)
.addOnCompleteListener(MainActivity.this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
hideProgressDialog();
Toast.makeText(MainActivity.this, "createUserWithEmail:onComplete:" + task.isSuccessful(), Toast.LENGTH_SHORT).show();
// If sign in fails, display a message to the user. If sign in succeeds
// the auth state listener will be notified and logic to handle the
// signed in user can be handled in the listener.
if (!task.isSuccessful()) {
Toast.makeText(MainActivity.this, "Authentication failed." + task.getException().toString().substring(task.getException().toString().indexOf(" ")),
Toast.LENGTH_SHORT).show();
Log.i("Error", task.getException().toString());
} else {
onAuthSuccess(task.getResult().getUser());
showUserList();
}
}
});
} else {
mAuth.signInWithEmailAndPassword(email,password)
.addOnCompleteListener(MainActivity.this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
hideProgressDialog();
// If sign in fails, display a message to the user. If sign in succeeds
// the auth state listener will be notified and logic to handle the
// signed in user can be handled in the listener.
if (!task.isSuccessful()) {
// there was an error
Toast.makeText(MainActivity.this, task.getException().toString(),
Toast.LENGTH_LONG).show();
} else
{
onAuthSuccess(task.getResult().getUser());
showUserList();
}
}
});
}
}
public void showUserList(){
startActivity(new Intent(getApplicationContext(), UserList.class));
finish();
}
private void onAuthSuccess(FirebaseUser user) {
String username = usernameFromEmail(user.getEmail());
// Write new user
writeNewUser(user.getUid(), username, user.getEmail());
// Go to MainActivity
}
private String usernameFromEmail(String email) {
if (email.contains("#")) {
return email.split("#")[0];
} else {
return email;
}
}
private void writeNewUser(String userId, String name, String email) {
User user = new User(name, email);
mDatabase.child("users").child(userId).setValue(user);
ArrayList<String> userNames = new ArrayList<>();
userNames.add(name);
mDatabase.child("usernamelist").setValue(userNames);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAuth = FirebaseAuth.getInstance();
mDatabase = FirebaseDatabase.getInstance().getReference();
if(mAuth.getCurrentUser()!=null){
showUserList();
}
usernameField = (EditText) findViewById(R.id.username);
passwordField = (EditText) findViewById(R.id.password);
changeSignUpModeTextView = (TextView) findViewById(R.id.changeSignUpMode);
signUpButton = (Button) findViewById(R.id.signupbutton);
logo = (ImageView)findViewById(R.id.logo);
relativeLayout= (RelativeLayout)findViewById(R.id.relativeLayout);
signUpModeActive = true;
changeSignUpModeTextView.setOnClickListener(this);
usernameField.setOnKeyListener(this);
passwordField.setOnKeyListener(this);
logo.setOnClickListener(this);
relativeLayout.setOnClickListener(this);
}
}
UserList.java
public class UserList extends AppCompatActivity {
private static final String TAG = "UserList" ;
private DatabaseReference userlistReference;
private ValueEventListener mUserListListener;
ArrayList<String> usernamelist = new ArrayList<>();
ArrayAdapter arrayAdapter;;
ListView userListView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
userlistReference = FirebaseDatabase.getInstance().getReference().child("usernamelist");
onStart();
userListView = (ListView) findViewById(R.id.userlistview);
}
#Override
protected void onStart() {
super.onStart();
final ValueEventListener userListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
usernamelist = new ArrayList<>((ArrayList) dataSnapshot.getValue());
usernamelist.remove(usernameOfCurrentUser());
Log.i(TAG, "onDataChange: "+usernamelist.toString());
arrayAdapter = new ArrayAdapter(UserList.this,android.R.layout.simple_list_item_1,usernamelist);
userListView.setAdapter(arrayAdapter);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "onCancelled: ",databaseError.toException());
Toast.makeText(UserList.this, "Failed to load User list.",
Toast.LENGTH_SHORT).show();
}
};
userlistReference.addValueEventListener(userListener);
mUserListListener = userListener;
}
public String usernameOfCurrentUser()
{
String email = MainActivity.mAuth.getCurrentUser().getEmail();
if (email.contains("#")) {
return email.split("#")[0];
} else {
return email;
}
}
#Override
public void onStop() {
super.onStop();
// Remove post value event listener
if (mUserListListener != null) {
userlistReference.removeEventListener(mUserListListener);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.action_logout:
FirebaseAuth.getInstance().signOut();
startActivity(new Intent(this, MainActivity.class));
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
You can use Google Identity Toolkit API to get a list of all registered users in your Firebase project, this API is used by the Firebase CLI which can be accessed by running firebase auth:export results-file
Make sure Identity Toolkit API is enabled
firebase-users-list.js
const serviceAccount = require('path/to/firebase-sdk-json-service-account');
const googleapis = require('googleapis');
const identitytoolkit = googleapis.identitytoolkit('v3');
const authClient = new googleapis.auth.JWT(
serviceAccount.client_email,
null,
serviceAccount.private_key,
['https://www.googleapis.com/auth/firebase'],
null
);
authClient.authorize((err) => {
if (err) {
return console.error(err);
}
let nextPageToken = undefined;
let users = [];
const getAccounts = () => identitytoolkit.relyingparty.downloadAccount({
auth: authClient,
resource: {
targetProjectId: serviceAccount.project_id,
maxResults: 200,
nextPageToken: nextPageToken
}
}, (e, results) => {
if (e) {
return console.error(err);
}
users = users.concat(results.users);
if (results.nextPageToken) {
nextPageToken = results.nextPageToken;
return getAccounts();
} else {
console.log(users);
}
});
getAccounts();
});
It's possible to use cloud function to fetch users list (view docs at firebase). Note, in the following example custom claims feature is used to check if user has enough privileges.
// USERS: return full users list for admin
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
import * as admin from 'firebase-admin'
import * as functions from 'firebase-functions'
export const listUsers = functions.https.onCall((data, context) => {
// check if user is admin (true "admin" custom claim), return error if not
const isAdmin = context.auth.token.admin === true
if (!isAdmin) {
return { error: `Unauthorized.` }
}
return admin
.auth()
.listUsers()
.then((listUsersResult) => {
// go through users array, and deconstruct user objects down to required fields
const result = listUsersResult.users.map((user) => {
const { uid, email, photoURL, displayName, disabled } = user
return { uid, email, photoURL, displayName, disabled }
})
return { result }
})
.catch((error) => {
return { error: 'Error listing users' }
})
})
You can do it using admin.auth().listUsers
Here is the doc for this: https://firebase.google.com/docs/reference/admin/node/admin.auth.Auth.html#listusers
And an usage example: https://firebase.google.com/docs/auth/admin/manage-users#list_all_users
function listAllUsers(nextPageToken) {
// List batch of users, 1000 at a time.
admin.auth().listUsers(1000, nextPageToken)
.then(function(listUsersResult) {
listUsersResult.users.forEach(function(userRecord) {
console.log('user', userRecord.toJSON());
});
if (listUsersResult.pageToken) {
// List next batch of users.
listAllUsers(listUsersResult.pageToken);
}
})
.catch(function(error) {
console.log('Error listing users:', error);
});
}
// Start listing users from the beginning, 1000 at a time.
listAllUsers();
i will answer it simply as much as possible
just add the registered user to your data base by using the following code
you can also use shared prefernces to save the data locally but it won't be able for other user.
once you save the list of user in the database simply retrieve it from there using adapters
FirebaseDatabase.getInstance().getReference().child("my_user")
.child(task.getResult().getUser().getUid())
.child("username").setValue(autoCompleteTextView1.getText().toString());
Users list in python:
from firebase_admin import credentials, db, auth
cred = credentials.Certificate('\path\to\serviceAccountKey.json')
default_app = firebase_admin.initialize_app(cred, {
"databaseURL": "https://data_base_url.firebaseio.com"
})
users = auth.list_users()
I have a very tiny node script to create a public/private key
Is there any way to do it on the client side without having to browserify hole crypto module?
var crypto = require('crypto');
var userCurve = crypto.createECDH('prime256v1');
var userPublicKey = userCurve.generateKeys()
var userPrivateKey = userCurve.getPrivateKey();
I have tried this so far:
// https://github.com/diafygi/webcrypto-examples#ecdh---generatekey
window.crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-256", //can be "P-256", "P-384", or "P-521"
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["deriveKey", "deriveBits"] //can be any combination of "deriveKey" and "deriveBits"
)
.then(function(key){
//returns a keypair object
console.log(key);
console.log(key.publicKey);
console.log(key.privateKey);
})
.catch(function(err){
console.error(err);
});
But it looks nothing like the node version when i log it
Let's do a complete elliptic curve Diffie-Hellman (ECDH) exchange to establish a shared secret between two parties. Alice uses Node.js and Bob sits at his browser (a recent version of Chrome or Firefox). (No need to browserify anything.)
(1) Alice generates a private and public key.
const crypto = require('crypto');
const alice = crypto.createECDH('prime256v1');
alice.generateKeys()
const alicePublicKey = alice.getPublicKey('hex')
const alicePrivateKey = alice.getPrivateKey('hex')
console.log(`publicKey: ${alicePublicKey}`)
console.log(`privateKey: ${alicePrivateKey}`)
Example output:
publicKey: 043a3770a8068738ded16c9409e1a6fbf6dde2360ac5b3fd3e5bb8d9fd6adaed6ea83ff5153f58ae13098e86da89df1beb14ef46388d3df76e8fe2ee0ff9e926d5
privateKey: 03ce9cb317c8761699f174943dc9b2d2b7991515b48216a4c677fcf5ee879f2c
(2) Alice sends her public key to Bob (043a3770...). Bob has written some helpers to convert hex strings to Uint8Arrays and buffers to hex strings.
const hex2Arr = str => {
if (!str) {
return new Uint8Array()
}
const arr = []
for (let i = 0, len = str.length; i < len; i+=2) {
arr.push(parseInt(str.substr(i, 2), 16))
}
return new Uint8Array(arr)
}
const buf2Hex = buf => {
return Array.from(new Uint8Array(buf))
.map(x => ('00' + x.toString(16)).slice(-2))
.join('')
}
(3) Bob receives Alices's key and computes the shared secret
He generates his own private and public key
He exports his public key and sends it to Alice
He imports Alice's public key
He computes the shared secret using his private and Alice's public key
// Alice's public key (received over an [insecure] connection)
const alicePublicKeyHex = '043a3770a8068738ded16c9409e1a6fbf6dde2360ac5b3fd3e5bb8d9fd6adaed6ea83ff5153f58ae13098e86da89df1beb14ef46388d3df76e8fe2ee0ff9e926d5'
const alicePublicKey = hex2Arr(alicePublicKeyHex)
console.log(`Alice's publicKey: ${alicePublicKeyHex}`)
let bob = null
// generate Bob's private and public key
window.crypto.subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'P-256'
},
false, // no need to make Bob's private key exportable
['deriveKey', 'deriveBits'])
.then(bobKey => {
bob = bobKey
// export Bob's public key
return window.crypto.subtle.exportKey(
'raw', bobKey.publicKey
)
})
.then(bobPublicKeyExported => {
const bobPublicKeyHex = buf2Hex(bobPublicKeyExported)
// display and send Bob's public key to Alice
console.log(`Bob's publicKey: ${bobPublicKeyHex}`)
// import Alice's public key
return window.crypto.subtle.importKey(
'raw',
alicePublicKey,
{
name: 'ECDH',
namedCurve: 'P-256'
},
true,
[])
})
.then(aliceKeyImported => {
// use Alice's imported public key and
// Bob's private key to compute the shared secret
return window.crypto.subtle.deriveBits(
{
name: 'ECDH',
namedCurve: 'P-256',
public: aliceKeyImported
},
bob.privateKey,
256)
})
.then(sharedSecret => {
const sharedSecretHex = buf2Hex(sharedSecret)
console.log(`sharedSecret: ${sharedSecretHex}`)
})
.catch(err => {
console.log(err)
})
Example output:
Alice's publicKey: 043a3770a8068738ded16c9409e1a6fbf6dde2360ac5b3fd3e5bb8d9fd6adaed6ea83ff5153f58ae13098e86da89df1beb14ef46388d3df76e8fe2ee0ff9e926d5
Bob's publicKey: 04aeceba6ae783c9b705833c2fa8822281f47f6f36bc867e4d398fa7a744d4fc63a010cbce1e6c9ac8858ad376a24ee8551615560f01c8bb63c86335c046b18962
sharedSecret: c26c9f370f001a947d7fec4dc9282d3e9ea718e1de487eb4f6fa7d6f0a311b97
(4) Alice receives Bob's public key (04aece...). She computes the shared secret as well.
const crypto = require('crypto')
const alice = crypto.createECDH('prime256v1')
// Alice's privateKey (generated previously)
const alicePrivateKey = '937cdd11062b612ff3cb3e4a3c183254b9728b4c8c3a64de799ed196b672734b'
// Bob's publicKey transmitted to Alice
const bobPublicKey = '04aeceba6ae783c9b705833c2fa8822281f47f6f36bc867e4d398fa7a744d4fc63a010cbce1e6c9ac8858ad376a24ee8551615560f01c8bb63c86335c046b18962'
// set Alice's private key (not needed if continuing from (1))
alice.setPrivateKey(alicePrivateKey, 'hex')
const sharedSecret = alice.computeSecret(bobPublicKey, 'hex', 'hex')
console.log(`sharedSecret: ${sharedSecret}`)
Examle output:
sharedSecret: c26c9f370f001a947d7fec4dc9282d3e9ea718e1de487eb4f6fa7d6f0a311b97
The secret is shared (the same).
(5) The shared secret is typically used to derive a symmetric key for encrypting messages between Alice and Bob (and they communicated happily ever after).
Remarks:
Usually there is no need to display or export the private key. Alice would typically continue the computation of the shared secret from step (1) (and omit alice.setPrivateKey(alicePrivateKey, 'hex')).
As the shared secret is most often used to derive a symmetric key, there is window.crypto.subtle.deriveKey and deriveBits can be ommitted. deriveBits was used here to illustrate that Alice and Bob indeed agreed on a shared secret.