【问题标题】:Migrating salted sha512 passwords from symfony 2 to firebase authentication将加盐 sha512 密码从 symfony 2 迁移到 firebase 身份验证
【发布时间】:2021-07-16 15:20:13
【问题描述】:

我正在尝试将用户(包括密码)从旧的 symfony 2 应用程序迁移到 firebase 身份验证(或 google 身份平台)。

在 symfony2 应用程序中,用户的密码使用带有盐的 sha512 进行哈希处理。我已经发现可以在 firebase (https://firebase.google.com/docs/auth/admin/import-users) 的文档中使用他们的密码和哈希来导入用户。然而,firebase 使用的 sha512 散列似乎与 symfony 使用的不一样。

对于旧的 symfony 项目,使用以下配置:

security:
    encoders:
        FOS\UserBundle\Model\UserInterface: sha512

通过查看源代码,我发现 symfony 给定了盐和密码 symfony 会产生这样的哈希:(在 python 代码中)

def get_hash(salt, password):
    hash = password.encode('utf-8')
    salted = hash + salt
    hash = hashlib.sha512(salted).digest()
    for i in range(1, 5000):
        # symfony keeps adding salted for every iteration, this is something firebase does not it seems
        hash = hashlib.sha512(hash + salted).digest()
    return base64.b64encode(hash).decode('utf-8')

但是,当我像下面的代码一样导入它时,此代码不允许我登录。然而,它产生的哈希值与我在 symfony2 应用程序的数据库中的哈希值相同:

app = firebase_admin.initialize_app()
salt = '{test}'.encode('utf-8')
hash = get_hash(salt=salt, password='xyz')
print('calculated hash', base64.b64encode(hash))
users = [
    auth.ImportUserRecord(
        uid='foobar',
        email='foo@bar.com',
        password_hash=hash,
        password_salt=salt
    )
]
hash_alg = auth.UserImportHash.sha512(rounds=5000)
try:
    result = auth.import_users(users, hash_alg=hash_alg)
    for err in result.errors:
        print('Failed to import user:', err.reason)
except exceptions.FirebaseError as error:
    print('Error importing users:', error)

但是,当我使用以下功能时,我可以使用密码登录。

def get_hash(salt, password):
    hash = password.encode('utf-8')
    salted = salt + hash
    hash = hashlib.sha512(salted).digest()
    for i in range(1, 5000):
        hash = hashlib.sha512(hash).digest()
    return hash

我已经找到了一种方法来更改添加盐的顺序,但我无法在 firebase hash = hashlib.sha512(hash + salted).digest() 中找到这样的哈希方法。

现在似乎没有办法将我的密码迁移到 firebase,因为 symfony 的实现与 firebase 使用的有点不同。有谁知道一种方法来确保我仍然可以导入我当前的哈希值?这会很棒。

如果没有,还有什么替代方法?

  • 是否可以让 firebase 向我自己的端点发出请求以验证密码。

  • 另一种方法是尝试捕获登录过程并首先将其发送到我自己的端点,在后台设置密码,然后将请求发送到 firebase?

【问题讨论】:

    标签: firebase


    【解决方案1】:

    您尚未指定您的客户端应用程序正在使用什么,所以我只是假设它是一个将使用 Firebase Web SDK 的网络应用程序。

    要使用此解决方案,您需要将 Symfony 用户数据迁移到私有 _migratedSymfonyUsers 集合下的 Firestore,其中每个文档都是该用户的电子邮件。

    在客户端,流程是:

    1. 从用户那里收集电子邮件和密码
    2. 尝试使用该电子邮件和密码组合登录 Firebase
    3. 如果失败,请使用该电子邮件和密码组合调用 Callable Cloud Function
    4. 如果函数返回成功消息(见下文),请使用给定的电子邮件和密码重新尝试登录用户
    5. 适当处理成功/错误

    在客户端,这看起来像:

    const legacySignIn = firebase.functions().httpsCallable('legacySignIn');
    
    async function doSignIn(email, password) {
      try {
        return await firebase.auth()
          .signInWithEmailAndPassword(email, password);
      } catch (fbError) {
        if (fbError.code !== "auth/user-not-found")
          return Promise.reject(fbError);
      }
    
      // if here, attempt legacy sign in
      const response = await legacySignIn({ email, password });
      
      // if here, migrated successfully
      return firebase.auth()
        .signInWithEmailAndPassword(email, password);
    }
    
    // usage:
    doSignIn(email, password)
      .then(() => console.log('successfully logged in/migrated'))
      .catch((err) => console.error('failed to log in', err));
    

    在可调用云函数中:

    1. (可选)使用App Check 断言请求来自您的应用程序
    2. 提供了断言电子邮件和密码,如果没有则抛出错误。
    3. 断言给定的电子邮件存在于您迁移的用户中,如果不存在则抛出错误。
    4. 如果在迁移的用户中,对密码进行哈希处理并与存储的哈希值进行比较。
    5. 如果哈希值不匹配,则抛出错误。
    6. 如果哈希匹配,则使用该电子邮件和密码组合创建一个新的 Firebase 用户
    7. 创建后,删除迁移的哈希并向调用者返回成功消息

    在服务器上,这看起来像:

    const functions = require('firebase-functions');
    const admin = require('firebase-admin');
    
    function symfonyHash(pwd, salt) {
      // TODO: Hash function
      return /* calculatedHash */;
    }
    
    exports.legacySignIn = functions.https.onCall(async (data, context) => {
      if (context.app == undefined) { // OPTIONAL
        throw new functions.https.HttpsError(
            'failed-precondition',
            'The function must be called from an App Check verified app.');
      }
    
      if (!data.email || !data.password) {
        throw new functions.https.HttpsError(
            'invalid-argument',
            'An email-password combination is required');
      }
    
      if (data.email.indexOf("/") > -1) {
        throw new functions.https.HttpsError(
            'invalid-argument',
            'Email contains forbidden character "/"');
      }
    
      const migratedUserSnapshot = await admin.firestore()
        .doc(`_migratedSymfonyUsers/${data.email}`);
    
      if (!migratedUserSnapshot.exists) {
        throw new functions.https.HttpsError(
            'not-found',
            'No user matching that email address was found');
      }
    
      const storedHash = migratedUserSnapshot.get("hash");
      const calculatedHash = symfonyHash(password, salt);
    
      if (storedHash !== calculatedHash) {
        throw new functions.https.HttpsError(
            'permission-denied',
            'Given credential combination doesn\'t match');
      }
     
      // if here, stored and calculated hashes match, migrate user
    
      // get migrated user data
      const { displayName, roles } = migratedUserSnapshot.data();
    
      // create the user based on migrated data
      const newUser = await admin.auth().createUser({
        email,
        password,
        ...(displayName ? { displayName } : {})
      });
    
      if (roles) { // <- OPTIONAL
        const roleMap = {
          "symfonyRole": "tokenRole",
          "USERS_ADMIN": "isAdmin",
          // ...
        }
        
        const newUserRoles = [];
        roles.forEach(symfonyRole => {
          if (roleMap[symfonyRole]) {
            newUserRoles.push(roleMap[symfonyRole]);
          }
        });
    
        if (newUserRoles.length > 0) {
          // migrate roles to user's token
          await setCustomUserClaims(
            newUser.uid,
            newUserRoles.reduce((acc, r) => { ...acc, [r]: true }, {})
          );
        }
      }
    
      // remove the old user data now that we're done with it.
      await hashSnapshot.ref.delete();
    
      // return success to client
      return { success: true };
    });
    

    【讨论】:

      猜你喜欢
      • 2021-03-15
      • 2017-03-18
      • 2011-05-08
      • 2021-09-27
      • 1970-01-01
      • 2015-12-28
      • 1970-01-01
      • 2020-08-01
      • 1970-01-01
      相关资源
      最近更新 更多