版本兼容矩阵
| 版本 |
关键差异 |
| Oracle 9i / 10g |
密码文件由 orapwd 生成,REMOTE_LOGIN_PASSWORDFILE=EXCLUSIVE;密码大小写不敏感 |
| Oracle 11g |
引入 sec_case_sensitive_logon(默认 TRUE),密码开始区分大小写 |
| Oracle 12c 及以上 |
多租户架构,SYS 为公共用户,须在 CDB$ROOT 下修改;密码文件格式升级,主备库不一致会报 ORA-46952 |
| Oracle 18c / 19c |
密码文件默认位置不变,orapwd 支持 format=12 显式指定 |
| Oracle 21c / 23ai |
强化密码策略,弱口令易被 profile 的 ora12c_verify_function 拒绝 |
脚本
#!/usr/bin/env bash
# ============================================================
# Oracle 管理员(SYS/SYSTEM)密码修改工具
# 用法:bash ora_chpass.sh --help
# ============================================================
set -uo pipefail
SCRIPT_NAME=$(basename "$0")
SCRIPT_VERSION="1.0.0"
usage(){
cat <<EOF
${SCRIPT_NAME} ${SCRIPT_VERSION} — Oracle 管理员密码修改工具
用法:
bash ${SCRIPT_NAME} [选项]
选项:
-h, --help 显示本帮助并退出(不连接数据库)
-V, --version 显示脚本版本并退出
-u, --user <用户名> 要改密的账号,默认 SYS
-p, --password <新密码> 新密码;不提供则交互式输入(推荐)
--old-password <旧密码>
旧密码,仅当 OS 认证不可用时用于建立连接
-s, --sid <实例名> 指定 ORACLE_SID
-O, --oracle-home <路径> 指定 ORACLE_HOME
--rebuild-pwfile 改密后重建密码文件(需要停库,谨慎使用)
--entries <数量> 重建密码文件时的条目数,默认 10
--no-verify 跳过改密后的登录验证
--dry-run 只打印将要执行的语句,不实际执行
-- 结束选项解析
环境变量:
ORACLE_NEW_PASS 等价于 --password
ORACLE_OLD_PASS 等价于 --old-password
ORACLE_SID / ORACLE_HOME 未通过选项指定时使用
连接方式(按顺序自动尝试):
1. 本地 OS 认证:sqlplus / as sysdba
2. 密码文件认证:sqlplus <user>/<old> as sysdba(需提供 --old-password)
3. 均失败时提示使用 --rebuild-pwfile 重建密码文件
版本兼容:
Oracle 9i / 10g 密码大小写不敏感,密码文件由 orapwd 生成
Oracle 11g sec_case_sensitive_logon 默认 TRUE,密码区分大小写
Oracle 12c 及以上 多租户,SYS 为公共用户,须在 CDB\$ROOT 下修改
Oracle 18c / 19c 密码文件格式升级,主备库不一致会报 ORA-46952
Oracle 21c / 23ai 密码策略更严格,弱口令可能被 profile 拒绝
执行前提:
1. 以 oracle 用户或具备 dba 组权限的账号执行
2. 改密本身不需要停库;仅 --rebuild-pwfile 需要停库
3. Data Guard 环境改密后需同步密码文件到备库
4. RAC 环境需将密码文件分发到所有节点
退出码:
0 成功
1 执行失败
2 参数错误
示例:
bash ${SCRIPT_NAME} --help
bash ${SCRIPT_NAME} -u SYS
bash ${SCRIPT_NAME} -u SYSTEM -s ORCL -p 'N3wP@ssw0rd'
bash ${SCRIPT_NAME} -u SYS --old-password 'oldpass'
bash ${SCRIPT_NAME} -u SYS --rebuild-pwfile --entries 20
bash ${SCRIPT_NAME} --dry-run
EOF
}
err(){ echo "[错误] $*" >&2; }
info(){ echo "[信息] $*"; }
warn(){ echo "[警告] $*" >&2; }
# ---------- 参数解析 ----------
DB_USER="SYS"
NEW_PASS="${ORACLE_NEW_PASS:-}"
OLD_PASS="${ORACLE_OLD_PASS:-}"
ORACLE_SID_ARG=""
ORACLE_HOME_ARG=""
REBUILD_PW=0
ENTRIES=10
DO_VERIFY=1
DRY_RUN=0
while [ $# -gt 0 ]; do
case "$1" in
-h|--help) usage; exit 0 ;;
-V|--version) echo "${SCRIPT_NAME} ${SCRIPT_VERSION}"; exit 0 ;;
-u|--user) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; DB_USER="$2"; shift 2 ;;
--user=*) DB_USER="${1#*=}"; shift ;;
-p|--password) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; NEW_PASS="$2"; shift 2 ;;
--password=*) NEW_PASS="${1#*=}"; shift ;;
--old-password) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; OLD_PASS="$2"; shift 2 ;;
--old-password=*) OLD_PASS="${1#*=}"; shift ;;
-s|--sid) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; ORACLE_SID_ARG="$2"; shift 2 ;;
--sid=*) ORACLE_SID_ARG="${1#*=}"; shift ;;
-O|--oracle-home) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; ORACLE_HOME_ARG="$2"; shift 2 ;;
--oracle-home=*) ORACLE_HOME_ARG="${1#*=}"; shift ;;
--rebuild-pwfile) REBUILD_PW=1; shift ;;
--entries) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; ENTRIES="$2"; shift 2 ;;
--entries=*) ENTRIES="${1#*=}"; shift ;;
--no-verify) DO_VERIFY=0; shift ;;
--dry-run) DRY_RUN=1; shift ;;
--) shift; break ;;
-*) err "未知选项:$1"; echo "使用 ${SCRIPT_NAME} --help 查看帮助" >&2; exit 2 ;;
*) err "多余的参数:$1"; echo "使用 ${SCRIPT_NAME} --help 查看帮助" >&2; exit 2 ;;
esac
done
[ $# -gt 0 ] && { err "多余的参数:$*"; exit 2; }
# ---------- 环境准备 ----------
[ -n "$ORACLE_HOME_ARG" ] && export ORACLE_HOME="$ORACLE_HOME_ARG"
[ -n "$ORACLE_SID_ARG" ] && export ORACLE_SID="$ORACLE_SID_ARG"
if [ -z "${ORACLE_HOME:-}" ]; then
for h in /u01/app/oracle/product/*/dbhome* /opt/oracle/product/*/dbhome*; do
[ -x "$h/bin/sqlplus" ] && export ORACLE_HOME="$h" && break
done
fi
[ -z "${ORACLE_HOME:-}" ] && { err "未找到 ORACLE_HOME,请用 -O 指定"; exit 1; }
[ -x "$ORACLE_HOME/bin/sqlplus" ] || { err "$ORACLE_HOME/bin/sqlplus 不存在"; exit 1; }
export PATH="$ORACLE_HOME/bin:$PATH"
SQLPLUS="$ORACLE_HOME/bin/sqlplus"
if [ -z "${ORACLE_SID:-}" ]; then
if [ -f /etc/oratab ]; then
ORACLE_SID=$(awk -F: '$3=="Y"{print $1; exit}' /etc/oratab)
fi
fi
[ -z "${ORACLE_SID:-}" ] && { err "未确定 ORACLE_SID,请用 -s 指定"; exit 1; }
export ORACLE_SID
info "ORACLE_HOME:$ORACLE_HOME"
info "ORACLE_SID :$ORACLE_SID"
# ---------- 密码输入与校验 ----------
if [ -z "$NEW_PASS" ]; then
printf "请输入 %s 的新密码:" "$DB_USER"
stty -echo 2>/dev/null
read -r NEW_PASS
stty echo 2>/dev/null
echo
fi
[ -z "$NEW_PASS" ] && { err "新密码不能为空"; exit 2; }
case "$NEW_PASS" in
*'"'*) err "密码不能包含双引号"; exit 2 ;;
*"'"*) err "密码不能包含单引号"; exit 2 ;;
esac
case "$DB_USER" in
*'"'*|*"'"*|*" "*) err "用户名不合法"; exit 2 ;;
esac
# ---------- 输出 SQL ----------
info "目标账号:$DB_USER"
if [ "$DRY_RUN" -eq 1 ]; then
echo "----- 将执行(dry-run,未实际执行)-----"
echo "ALTER USER \"$DB_USER\" IDENTIFIED BY \"******\";"
[ "$REBUILD_PW" -eq 1 ] && \
echo "orapwd file=\$ORACLE_HOME/dbs/orapw$ORACLE_SID password=\"******\" entries=$ENTRIES force=y"
echo "---------------------------------------"
exit 0
fi
# ---------- 连接方式探测 ----------
AUTH_MODE=""
probe_os_auth(){
local out
out=$("$SQLPLUS" -s /nolog 2>&1 <<'EOF'
SET HEADING OFF FEEDBACK OFF PAGESIZE 0 TRIMSPOOL ON
WHENEVER SQLERROR EXIT SQL.SQLCODE
CONNECT / AS SYSDBA
SELECT 'OSAUTH_OK' FROM dual;
EXIT
EOF
)
case "$out" in *OSAUTH_OK*) return 0 ;; *) return 1 ;; esac
}
probe_pwfile_auth(){
[ -z "$OLD_PASS" ] && return 1
local out
out=$("$SQLPLUS" -s /nolog 2>&1 <<EOF
SET HEADING OFF FEEDBACK OFF PAGESIZE 0 TRIMSPOOL ON
WHENEVER SQLERROR EXIT SQL.SQLCODE
CONNECT $DB_USER/"$OLD_PASS" AS SYSDBA
SELECT 'PWFILE_OK' FROM dual;
EXIT
EOF
)
case "$out" in *PWFILE_OK*) return 0 ;; *) return 1 ;; esac
}
if probe_os_auth; then
AUTH_MODE="os"
info "连接方式:本地 OS 认证"
elif probe_pwfile_auth; then
AUTH_MODE="pwfile"
info "连接方式:密码文件认证"
else
err "无法以 SYSDBA 身份连接数据库"
err "OS 认证失败,且未提供可用的 --old-password"
warn "如需在完全无法登录时重置,请使用 --rebuild-pwfile 重建密码文件"
exit 1
fi
# ---------- 生成并执行改密语句 ----------
CONNECT_LINE="CONNECT / AS SYSDBA"
[ "$AUTH_MODE" = "pwfile" ] && CONNECT_LINE="CONNECT $DB_USER/\"$OLD_PASS\" AS SYSDBA"
run_alter(){
"$SQLPLUS" -s /nolog 2>&1 <<EOF
SET HEADING OFF FEEDBACK OFF PAGESIZE 0 TRIMSPOOL ON ECHO OFF
WHENEVER SQLERROR EXIT SQL.SQLCODE
$CONNECT_LINE
ALTER USER "$DB_USER" IDENTIFIED BY "$NEW_PASS";
SELECT 'ALTER_OK' FROM dual;
EXIT
EOF
}
OUT=$(run_alter)
RC=$?
printf '%s\n' "$OUT"
if [ "$RC" -ne 0 ] || ! printf '%s\n' "$OUT" | grep -q 'ALTER_OK'; then
err "改密失败,请根据上面的 ORA- 错误定位"
case "$OUT" in
*ORA-28003*|*ORA-28001*) warn "密码复杂度不满足 profile 的密码校验函数,请改用更复杂的密码" ;;
*ORA-01031*) warn "权限不足,确认执行用户属于 dba 组或具备 SYSDBA" ;;
*ORA-01017*) warn "旧密码错误或密码文件不可用" ;;
*ORA-28000*) warn "账号被锁定,可先 ALTER USER $DB_USER ACCOUNT UNLOCK" ;;
esac
exit 1
fi
info "ALTER USER 执行成功"
# ---------- 可选:重建密码文件 ----------
if [ "$REBUILD_PW" -eq 1 ]; then
PWFILE="$ORACLE_HOME/dbs/orapw$ORACLE_SID"
[ -f "$ORACLE_HOME/dbs/orapw${ORACLE_SID}" ] || \
warn "未找到现有密码文件 $PWFILE,将直接创建"
warn "重建密码文件需要停止实例,请确认已具备维护窗口"
info "关闭实例..."
"$SQLPLUS" -s /nolog >/dev/null 2>&1 <<EOF
CONNECT / AS SYSDBA
SHUTDOWN IMMEDIATE
EXIT
EOF
sleep 3
info "备份旧密码文件(如存在)..."
[ -f "$PWFILE" ] && cp -a "$PWFILE" "${PWFILE}.bak.$(date +%Y%m%d%H%M%S)"
info "执行 orapwd 重建..."
if "$ORACLE_HOME/bin/orapwd" file="$PWFILE" password="$NEW_PASS" \
entries="$ENTRIES" force=y; then
info "密码文件重建成功:$PWFILE"
else
err "orapwd 执行失败"
fi
info "启动实例..."
"$SQLPLUS" -s /nolog >/dev/null 2>&1 <<EOF
CONNECT / AS SYSDBA
STARTUP
EXIT
EOF
fi
# ---------- 验证 ----------
if [ "$DO_VERIFY" -eq 1 ]; then
info "验证新密码登录..."
VOUT=$("$SQLPLUS" -s /nolog 2>&1 <<EOF
SET HEADING OFF FEEDBACK OFF PAGESIZE 0 TRIMSPOOL ON
WHENEVER SQLERROR EXIT SQL.SQLCODE
CONNECT $DB_USER/"$NEW_PASS" AS SYSDBA
SELECT 'VERIFY_OK' FROM dual;
EXIT
EOF
)
if printf '%s\n' "$VOUT" | grep -q 'VERIFY_OK'; then
info "验证通过:新密码可以正常登录"
else
warn "新密码验证失败,输出如下:"
printf '%s\n' "$VOUT"
warn "若字典可登录但密码文件失败,请加 --rebuild-pwfile 重建密码文件"
exit 1
fi
fi
info "完成"
exit 0
使用示例
bash ora_chpass.sh --help
bash ora_chpass.sh -u SYS
bash ora_chpass.sh -u SYSTEM -s ORCL -p 'N3wP@ssw0rd'
bash ora_chpass.sh -u SYS --old-password 'oldpass'
bash ora_chpass.sh -u SYS --rebuild-pwfile --entries 20
bash ora_chpass.sh --dry-run
关键点说明
| 项目 |
说明 |
| 改密不需要停库 |
ALTER USER 在线执行,对业务无中断;只有重建密码文件才需要 SHUTDOWN IMMEDIATE |
| 双密码源 |
数据字典与密码文件是两套凭证。字典改成功但远程 AS SYSDBA 仍报 ORA-01017 时,说明密码文件未同步,需 orapwd 重建 |
| OS 认证优先 |
本地 sqlplus / as sysdba 不依赖任何密码,是最可靠的入口;要求执行用户在 dba 组或 ORA_DBA 组 |
| 密码文件位置 |
Linux 为 $ORACLE_HOME/dbs/orapw$ORACLE_SID;Windows 为 %ORACLE_HOME%\database\PWD%ORACLE_SID%.ora,两者不能混用 |
| 密码复杂度 |
12c 起 profile 常启用 ora12c_verify_function,弱口令会被 ORA-28003 拒绝 |
| 密码含特殊字符 |
必须用双引号包裹,脚本已拒绝含单双引号的密码以避免转义问题 |
| 12c 多租户 |
SYS 是公共用户,在 CDB$ROOT 下修改即可;PDB 内的本地用户需切到对应容器 |
| 密码文件条目上限 |
条目数不足会报 ORA-01996: GRANT failed: password file is full,重建时用 --entries 放大 |
| 密码文件参数 |
需 REMOTE_LOGIN_PASSWORDFILE=EXCLUSIVE(或 SHARED)才启用密码文件认证;为 NONE 时远程 SYSDBA 无法登录 |
| 权限核对 |
用 SELECT * FROM V$PWFILE_USERS; 确认账号具备 SYSDBA 权限 |
高风险场景提示
- Data Guard 主备库:主库改密后必须把密码文件同步到备库。12c 之后若主备密码文件格式不一致,备库 MRP 会因 ORA-46952 终止,
Real Time Apply 停止。建议做法是主库改密后直接复制密码文件到备库,而不是在备库单独执行 orapwd。
- RAC 环境:密码文件通常放在共享存储(ASM 或集群文件系统)上,单节点修改即可;若各节点本地各有一份,必须逐节点同步,否则节点间 SYSDBA 登录行为不一致。
- ASM 实例:ASM 不支持直接
ALTER USER SYS IDENTIFIED BY,会触发 ORA-00600 [15051] 或 ORA-01109,必须停实例后用 orapwd 重建。
- 修改 SYSTEM 或其他 DBA 账号:与 SYS 不同,这些账号的密码只存在于数据字典,不影响密码文件;但若它们被授予了 SYSDBA,则密码文件中的条目需通过重建或重新
GRANT SYSDBA 保持一致。
- 改密后务必同步:应用连接串、监控采集、备份脚本(RMAN)、Data Pump 目录、自动化运维平台中的旧密码都要更新,否则会引发批量连接失败。