diff --git a/packages/backend/src/misc/password.ts b/packages/backend/src/misc/password.ts
new file mode 100644
index 000000000..c63f89f5c
--- /dev/null
+++ b/packages/backend/src/misc/password.ts
@@ -0,0 +1,20 @@
+import bcrypt from "bcryptjs";
+import * as argon2 from "argon2";
+
+export async function hashPassword(password: string): Promise<string> {
+	return argon2.hash(password);
+}
+
+export async function comparePassword(
+	password: string,
+	hash: string,
+): Promise<boolean> {
+	if (isOldAlgorithm(hash)) return bcrypt.compare(password, hash);
+
+	return argon2.verify(hash, password);
+}
+
+export function isOldAlgorithm(hash: string): boolean {
+	// bcrypt hashes start with $2[ab]$
+	return hash.startsWith("$2");
+}
diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts
index b06f47ed4..c8cd5fb2c 100644
--- a/packages/backend/src/server/api/private/signin.ts
+++ b/packages/backend/src/server/api/private/signin.ts
@@ -12,6 +12,7 @@ import {
 } from "@/models/index.js";
 import type { ILocalUser } from "@/models/entities/user.js";
 import { genId } from "@/misc/gen-id.js";
+import { comparePassword, hashPassword, isOldAlgorithm } from '@/misc/password.js';
 import { verifyLogin, hash } from "../2fa.js";
 import { randomBytes } from "node:crypto";
 import { IsNull } from "typeorm";
@@ -88,7 +89,12 @@ export default async (ctx: Koa.Context) => {
 	const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
 
 	// Compare password
-	const same = await bcrypt.compare(password, profile.password!);
+	const same = await comparePassword(password, profile.password!);
+
+	if (same && isOldAlgorithm(profile.password!)) {
+		profile.password = await hashPassword(password);
+		await UserProfiles.save(profile);
+	}
 
 	async function fail(status?: number, failure?: { id: string }) {
 		// Append signin history